说 C++ 标准中语法选项的表示没有隐含的顺序是否正确?

Is it correct to say that there is no implied ordering in the presentation of grammar options in the C++ Standard?

我会试着用一个例子来解释我的问题。考虑以下 C++ 标准中的语法产生式:

literal:
   integer-literal
   character-literal
   floating-point-literal
   string-literal
   boolean-literal
   pointer-literal
   user-defined-literal

一旦解析器将 文字 识别为 整数文字 ,我一直认为解析器会就此停止。但有人告诉我这是 正确的。解析器将继续解析以验证 literal 是否也可以与 user-defined-literal 匹配,例如。

这是正确的吗?

编辑

我决定将此编辑作为我对标准的解释,以回应@rici 在下面的出色回答,尽管结果与 OP 所倡导的相反。

可以在 [stmt.ambig]/1 和 /3 中阅读以下内容(重点是我的):

[stmt.ambig]/1

There is an ambiguity in the grammar involving expression-statements and declarations: An expression-statement with a function-style explicit type conversion as its leftmost subexpression can be indistinguishable from a declaration where the first declarator starts with a (. In those cases the statement is a declaration.

也就是说,这一段说明了应该如何处理语法中的歧义。 C++ 标准中提到了其他几个歧义,但我知道的只有三个是与语法相关的歧义,[stmt.ambig]、[dcl.ambig.res]/1, a direct consequence of [stmt.ambig] and [expr.unary.op]/10,其中明确说明了术语 歧义语法.

[stmt.ambig]/3:

The disambiguation is purely syntactic; that is, the meaning of the names occurring in such a statement, beyond whether they are type-names or not, is not generally used in or changed by the disambiguation. Class templates are instantiated as necessary to determine if a qualified name is a type-name. Disambiguation precedes parsing, and a statement disambiguated as a declaration may be an ill-formed declaration. If, during parsing, a name in a template parameter is bound differently than it would be bound during a trial parse, the program is ill-formed. No diagnostic is required. [ Note: This can occur only when the name is declared earlier in the declaration. — end note ]

好吧,如果歧义消除先于解析,则没有什么可以阻止体面的编译器通过考虑语法的每个定义中存在的替代项确实是有序的来优化解析。考虑到这一点,可以删除下面 [lex.ext]/1 中的第一句话。

[lex.ext]/1:

If a token matches both user-defined-literal and another literal kind, it is treated as the latter. [ Example: 123_­km is a user-defined-literal, but 12LL is an integer-literal. — end example ] The syntactic non-terminal preceding the ud-suffix in a user-defined-literal is taken to be the longest sequence of characters that could match that non-terminal.

另请注意,本段未提及 语法中的歧义,至少对我而言,这表明歧义不存在.

没有暗示或必要的排序。

七种字面意思各不相同。满足其中任何一个定义的标记都不能满足任何其他定义。 例如,42 是一个 整数文字 ,不能是一个浮点字面量.

编译器如何确定令牌是什么是标准未解决且不需要解决的实现细节。

如果有歧义,例如同一个标记可以是 整数文字用户定义文字, 要么语言必须有规则来消除歧义,要么就是语法中的错误。

更新:实际上存在这样的歧义。正如评论中所讨论的,42ULL 满足 整数文字 用户定义文字 的语法。这种歧义不是通过语法产生式的顺序来解决的,而是通过一个明确的声明来解决的:

If a token matches both user-defined-literal and another literal kind, it is treated as the latter.

标准中的section on syntactic notation只说了这个意思:

In the syntax notation used in this document, syntactic categories are indicated by italic type, and literal words and characters in constant width type. Alternatives are listed on separate lines except in a few cases where a long set of alternatives is marked by the phrase “one of”. If the text of an alternative is too long to fit on a line, the text is continued on subsequent lines indented from the first one. An optional terminal or non-terminal symbol is indicated by the subscript “opt”, so

{ expressionopt }

indicates an optional expression enclosed in braces.

请注意,该语句将语法中的术语视为 "alternatives",而不是列表甚至有序列表。根本没有关于 "alternatives" 排序的声明。

因此,这强烈表明根本没有排序。

事实上,整个标准中都存在用于消除多个术语匹配的情况的歧义的特定规则,这也表明备选方案没有写成优先列表。如果备选方案是某种有序列表,this statement would be redundant:

If a token matches both user-defined-literal and another literal kind, it is treated as the latter.

C++ 表示语法中没有隐式的产生式排序。

该语法中存在歧义,标准中的文本将根据具体情况逐一处理这些歧义。注意标准的文字是规范;语法不是独立的,也不会覆盖文本。两者需要一起阅读。

标准本身指出附录 A 中恢复的语法:

… is not an exact statement of the language. In particular, the grammar described here accepts a superset of valid C++ constructs. Disambiguation rules (8.9, 9.2, 11.8) must be applied to distinguish expressions from declarations. Further, access control, ambiguity, and type rules must be used to weed out syntactically valid but meaningless constructs. (Appendix A, paragraph 1)

这不是标准文本中解决的歧义的完整列表,因为还有关于词汇歧义的规则。 (见下文。)

几乎所有这些歧义解决子句的形式都是 "if both P and Q applies, choose Q",因此如果存在语法替代项的隐式排序就没有必要了,因为只需将替代项放在正确的顺序。因此,标准认为有必要将一些条款专门用于歧义解决这一事实 表面上看来 证明备选方案并未隐式排序。 [注1]

C++ 标准没有明确命名所使用的语法形式,但它确实归功于允许我们构建历史论证的前因。 C++ 标准使用的形式主义继承自 C 标准和 Kernighan & Ritchie 的原著中关于(当时刚刚出版的)C 语言的描述。 K&R 使用 Yacc 解析器生成器编写他们的语法,原始 C 语法基本上是一个 Yacc 语法文件。 Yacc 使用 LALR(1) 算法从上下文无关文法 (CFG) 构建解析器,它的文法文件是用后来被称为 BNF 编写的文法的具体表示(尽管历史上存在一些歧义) BNF 中的字母实际代表什么)。 BNF 没有任何隐含的规则排序,并且形式主义不允许任何方式来编写显式排序或任何其他消歧规则。 (BNF 文法必须明确才能进行机械解析;如果不明确,LALR(1) 算法将无法生成解析器。)

Yacc 确实有点超出常规。它有一些自动消歧规则,以及一种提供显式消歧(运算符优先级)的机制。但是 Yacc 的消歧也与备选方案的顺序无关。

简而言之,直到 2002 年 Bryan Ford 提出 Packrat 解析并随后将他称为 "Parsing Expression Grammars"(PEG)的 class 语法形式化之前,有序替代项并不是任何语法形式主义的真正特征. PEG 算法确实隐含地对备选方案进行排序,它坚持只有在左手备选方案不匹配时才尝试在备选方案中使用右手备选方案。因此,PEG 交替运算符(或 "ordered alternation" 运算符)通常写成 / 而不是 |,避免与传统的无序交替语法混淆。

PEG 算法的一个关键特征是它始终是确定性的。每个 PEG 文法都可以毫无歧义地确定性地应用于源文本。 (当然,这并不意味着语法会给你你想要的解析。这只是意味着它永远不会给你一个解析列表,而是让你 select 你想要的那个。)所以写的语法在 PEG 中不能伴随消除歧义的文本规则,因为没有歧义。

之所以提到这一点,是因为 PEG 的存在和流行在某种程度上改变了人们对交替运算符含义的理解。在 PEG 之前,我们可能根本不会进行此类讨论。但是使用 PEG 作为解释 C++ 语法形式主义的指南是不符合历史的和不合理的; C++ 语法的根源至少可以追溯到 1978 年,至少比 PEG 早四分之一个世纪。

词汇歧义,以及解决它们的从句

  1. [lex.pptoken] (§5.4) 第 3 段规定了令牌识别的基本规则,这比传统的 "maximal munch" 原则复杂一点,后者总是识别最长的可能的令牌紧接在先前识别的令牌之后。它包括两个例外:

    • 序列 <:: 被视为以标记 < 而不是较长的标记 <: 开始,除非它是 <::> 的开始(被视为 <::>) 或 <:::(视为 <:::)。如果您在脑海中将 <: 替换为 [ 并将 :> 替换为 ],这可能更有意义,这是预期的句法等价。
    • 原始字符串文字由 first 匹配的定界符序列终止。这条规则理论上可以用上下文无关文法来写,只是因为对终止序列的长度有明确的限制,这意味着理论上的 CFG 大约为 8816 规则,每个可能的定界符序列一个。在实践中,这个规则不能这样写,它是文本描述的,以及 d-char-sequence.
    • 长度的 16 个字符限制
  2. [lex-header] (§5.8) 避免了 header-namesstring-literals 之间的歧义(以及某些以 < 开头的标记序列)要求 header-name 仅在某些上下文中被识别,包括 #include 预处理指令。 (该部分实际上并没有说不应该识别string-literal,但我认为含义很清楚。)

  3. [lex.ext] (§5.13.8) 第 1 段通过要求:

    • user-defined-literal 规则仅在标记不能被识别为其他种类的文字时才被识别,并且
    • user-defined-literal 分解为后跟 ud-suffix 的文字遵循最长标记规则,如上所述。

    请注意,此规则并不是真正的标记化规则,因为它是在 源文本被划分为标记之后应用的。标记化在翻译阶段 3 完成,之后标记通过预处理指令(阶段 4)、转义序列和 UCN 的重写(阶段 5)以及字符串文字的连接(阶段 6)传递。从阶段 6 中出现的每个标记然后必须被重新解释为句法语法中的标记,并且在这一点上文字标记将被 class 化。因此,§5.13.8 没有必要阐明被分类的令牌的范围是什么;范围是已知的,转换后的令牌必须使用预处理令牌中的所有字符。因此,它与此列表中的其他歧义完全不同,但我将其留在此处,因为它在原始问题和各种评论线程中如此存在。

备注:

  1. 奇怪的是,在几乎所有的歧义解决条款中,首选选项是选项列表中较晚出现的选项。例如,§8.9 明确倾向于 declarations 而不是 expressions,但是 statement 的语法列出 表达式语句早于声明语句。话虽如此,正确解析 C++ 需要比 "try to parse a declaration and if that fails, then try to parse as an expression," 更复杂的算法,因为有些程序必须被解析为带有语法错误的声明(参见 [stmt.ambig]/3 处的示例)。