在C++标准中,语法选项的表示中没有隐含的排序,这种说法正确吗

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

本文关键字:排序 语法 标准 C++ 选项 表示      更新时间:2023-10-16

我将尝试用一个例子来解释我的问题。考虑C++标准中的以下语法生成:

文字
/emsp emsp整数文本
/emsp emsp字符文字
/emsp emsp浮点文字
/emsp emsp字符串文字
/emsp emsp布尔文字
/emsp emsp指针文字
/emsp emsp用户定义文字

一旦解析器将文本识别为整数文本本是否也可以与用户定义的文本匹配。

这是正确的吗?

编辑

我决定将此编辑作为我对标准的解释,以回应@rici在下面的精彩回答,尽管其结果与安大略省安大略省安大略厅倡导的结果相反。

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

[stmt.ambig]/1

语法中存在歧义涉及表达式语句和声明:具有函数样式显式类型转换作为其最左边的子表达式可以与第一个声明符所在的声明无法区分以(开头。在这种情况下,语句是一个声明。

也就是说,这一段说明了应该如何处理语法中的歧义。C++标准中还提到了其他几个模糊性,但我只知道三个是与语法有关的模糊性,[stmt.ambig]、[dcl.ambig.res]/1、[stmt.ambig]和[expr.unary.op]/10的直接结果,它们明确地说明了语法中的术语模糊性

[stmt.ambig]/3:

歧义的消除纯粹是句法上的;也就是说出现在这样一个陈述中的名字,除了它们是否类型名称,通常不在中使用或由消除歧义。根据需要实例化类模板确定限定名称是否为类型名称消除歧义在解析之前,并且作为声明消除歧义的语句可能是一个格式错误的声明。在解析过程中,如果模板中的名称参数的绑定方式与试验期间的绑定方式不同解析时,程序格式不正确。无需进行诊断。[注:只有在声明。--尾注]

好吧,如果在解析之前消除歧义,那么没有什么可以阻止一个像样的编译器通过考虑语法的每个定义中存在的备选方案确实是有序的来优化解析的。考虑到这一点,可以删除下文[lex.ext]/1中的第一句。

[lex.ext]/1:

如果一个令牌同时匹配用户定义的文字和另一种文字类型,它被视为后者。[示例:123_km是用户定义的文字,但12LL是一个整数文字。——结束示例]在一个用户定义的文字被视为最长的字符序列可以匹配非终端。

请注意,这段话在语法中没有提到歧义,至少对我来说,这表明歧义不存在

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

该语法中存在歧义,标准中的文本会根据具体情况进行处理。请注意,该标准的文本为规范性;语法并不是孤立的,它也不会凌驾于文本之上。这两者需要一起阅读。

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

…不是语言的精确表述。特别是,这里描述的语法接受有效C++构造的超集。必须适用消歧规则(8.9、9.2、11.8)来区分表述和声明。此外,必须使用访问控制、模糊性和类型规则来剔除语法上有效但无意义的结构。(附录A第1段)

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

几乎所有这些歧义解决子句的形式都是"如果pQ都适用,则选择Q",因此,如果存在语法备选项的隐式排序,则没有必要,因为只需将备选项按正确顺序排列就可以保证正确的解析。因此,该标准认为有必要专门用一些条款来解决歧义,这一事实是初步证据,证明备选方案并非隐含有序。[注1]

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

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

简言之,直到2002年Bryan Ford提出packrat语法分析,并随后将一类语法形式化,他称之为"语法分析表达式语法"(PEG),有序替换才真正成为任何语法形式主义的特征。PEG算法通过坚持只有在左侧备选方案不匹配的情况下才尝试在备选方案中使用右侧备选方案,从而隐含地对备选方案进行排序。因此,PEG交替运算符(或"有序交替"运算符)通常被写成/而不是|,以避免与传统的无序交替语法混淆。

PEG算法的一个关键特征是它总是具有确定性的。每个PEG语法都可以确定地应用于源文本,而不会产生歧义。(当然,这并不意味着语法会给你想要的解析。这只是意味着它永远不会给你一个解析列表,让你选择你想要的。)因此,用PEG编写的语法不能伴随着消除歧义的文本规则,因为不存在歧义。

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

词汇歧义及其解决方法

  1. [lex.pptoken](§5.4)第3段规定了令牌识别的基本规则,这比传统的"最大咀嚼"原则稍微复杂一些,传统的"最长咀嚼"原则总是从先前识别的令牌之后立即识别尽可能长的令牌。它包括两个例外:

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

  3. [lex.ext](§5.13.8)第1段解决了用户定义文字所涉及的歧义,要求:

    • 只有当令牌不能被识别为某种其他类型的文本时,才能识别用户定义的文本规则,并且
    • 用户定义的文字分解为文字,后跟ud后缀遵循上述最长令牌规则

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

注意:

  1. 奇怪的是,在几乎所有的歧义解决条款中,首选的替代方案是出现在替代方案列表后面的那个。例如§8.9明确地倾向于声明而不是表达式,但语句的语法早在宣告语句前就列出了表达语句

没有隐含或必要的排序。

所有七种文字都是不同的。符合其中任何一个定义的令牌都不能满足任何其他令牌的定义例如,42是一个整数文本,不能是

浮点文本编译器如何确定令牌是什么是一个实现细节,标准没有解决,也不需要解决

如果存在歧义,例如,同一个标记可以是整数文本

用户定义文本最新消息:事实上存在这样的歧义。正如评论中所讨论的,42ULL满足整数文本用户定义文本

如果一个令牌同时匹配用户定义的文字和另一种文字

标准中关于句法符号的部分只说明了它的含义:

在本文档中使用的语法表示法中,语法类别由斜体类型表示,文字单词和字符由constant width类型表示。备选方案列在单独的行上,除非在少数情况下,一长串备选方案用短语"其中之一"标记。如果备选方案的文本太长,无法放在一行中,则文本将在从第一行缩进的后续行中继续。可选终端或非终端符号由下标">opt"表示,因此

{expressionopt}

表示用大括号括起来的可选表达式。

注意,该语句将语法中的术语视为"替代项",而不是列表,甚至是有序列表。并没有任何关于"替代品"订购的声明。

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

事实上,在整个标准中存在特定规则来消除多个术语匹配的情况下的歧义,这也表明备选方案并不是作为优先列表来编写的。如果备选方案是某种有序列表,则此语句将是多余的:

如果一个令牌同时匹配用户定义的文字和另一种文字