冒着问一个被认为过于挑剔的问题的风险,我花了很长时间试图证明(作为整个标准在不同上下文中发生的事情的一个例子)C++11标准§2.14.2中integer literal
的以下定义,特别是关于一个细节,即语法符号本身中存在空格。
(请注意,这个例子-整数字面的定义-不是我问题的重点。我问题的要点是询问C++标准本身使用的语法描述符号,特别是语法类别名称之间的空格。我在这里给出的例子-整数文本的定义-是专门选择的,因为它是一个简单明了的例子。)
(简洁的缩写,来自§2.14.2):
integer-literal:
decimal-literal integer-suffix_opt
decimal-literal:
nonzero-digit
decimal-literal digit
(具有预期的nonzero-digit
和digit
,[0]1…9)。(注:以上文本均为标准中的斜体字。)
这一切对我来说都是有意义的,假设语法类别描述符decimal-literal
和digit
之间的空格被理解为不存在于实际源代码中,而仅存在于第2.14.2节中出现的语法描述本身中。
这种约定——在符号中的类别描述符之间放置一个空格,可以理解该空格不存在于源代码中——在规范的其他地方也使用。这里的例子只是一个明确的例子,其中空间显然不应该出现在源代码中。(有关标准中的反例,请参阅本问题的附录,其中当源代码中的类别描述符被实际标记替换时,类别描述符之间必须存在空格或其他分隔符/s,或者是可选的。)
同样,冒着挑剔的风险,我在标准中找不到任何约定声明,即在解释本例中的符号时,源代码中不应存在空格。
该标准确实在§1.6.1(及其后)中讨论了符号惯例。我能找到的关于这方面的唯一相关文本是:
在本国际标准中使用的语法表示法中类别由斜体表示,文字单词和等宽类型的字符。备选方案单独列出行,除非在少数情况下标记了一长串备选方案
我不会那么挑剔;然而,我发现标准中使用的符号有点棘手,所以我想弄清楚所有的细节。我很感激任何愿意花时间向我介绍这件事的人。
附录针对类似于">很明显,最终源代码中不应包含空白,因此标准没有必要明确说明这一点"的评论:我在这个问题中选择了一个微不足道的例子,其中是明显的。在标准中有许多情况下,如果没有对语言的先验知识(在我看来),它就不明显,例如§8.0.4讨论"const"one_answers"volatile":
cv-qualifier-seq:
cv-qualifier cv-qualifier-seq_opt
注意这里的与相反的假设(在最终源代码中,空格或另一个或多个分隔符是所必需的),但这不可能从语法表示法本身推导出来。
也有空间可选的情况,例如:
noptr-abstract-declarator:
noptr-abstract-declarator_opt parameters-and-qualifiers
(在这个例子中,为了表明一点,我不会给出章节编号或解释正在讨论的内容;我只会问,从语法符号本身来看,在这个上下文中,最终源代码中的空白在标记之间是可选的。)
我怀疑,这些评论——"这很明显,所以这一定是"——是我选择的例子如此明显的结果。这正是我选择这个例子的原因。
§2.7.1
有五种令牌:标识符、关键字、文本、,运算符和其他分隔符。空白,水平和垂直标签,换行符、表单提要和注释(统称为"空白"),作为下面描述的被忽略,除非它们用于分隔标记。
因此,如果一个文字是一个标记,而空白用于分隔标记,则文字数字之间的空格将被解释为两个单独的标记,因此不能是同一文字的一部分。
我有理由相信,在标准中没有更直接的解释来解释这一事实。
所使用的表示法与典型的BNF非常相似,它们认为许多相同的通用约定是理所当然的,包括表示法中的空白除了分隔BNF本身的标记之外没有任何意义——如果/当空白在源代码中具有超出分隔标记的意义时,它们将包括直接指定它的符号(例如,对于大多数预处理指令,直接指定new-line
:
#ifdef标识符新行组opt
或:
#包括<h-char-sequence>新行
的责任可能要追溯到Algol 68标准,该标准在精确指定语法方面做得太过火了,如果没有几周的全职学习,任何人基本上都不可能阅读1。从那时起,对语法描述语言的任何粗略解释都会被拒绝,因为它太像Algol 68了,而且毫无疑问会失败,因为它过于正式,没有人会阅读或理解它
1你问的怎么会这么糟糕?基本上是这样的:他们从语法描述语言的正式英语描述开始。不过,这并没有被用来定义Algol 68——它被用来指定(甚至更准确地说)另一种语法描述语言。然后使用第二个语法描述语言来指定Algol 68本身的语法。因此,您必须学习两种独立的语法描述语言,然后才能开始阅读Algol68语法本身。毫无疑问,你可以猜到,几乎没有人这样做过。
正如您所说,标准规定:
常宽型中的文字和字符
因此,如果要在规则中包含文字空间,则必须以恒定宽度类型呈现。仔细检查该标准会发现,您所指的生产中的空间比恒定宽度类型窄。(此外,您试图引用该标准是一种错误陈述,因为它以等宽类型呈现,而该等宽类型应以斜体呈现,从而导致语义变化。)
好的,这就是";有抱负的语言律师;答复此外,它并没有真正起作用,因为它在所有形式的产品上都失败了:
One of:
0 1 2 3 4 5 6 7 8 9
我认为,事实上,答案是空白不是形式语法的一部分,因为它只用于分隔标记;此外,这句话主要适用于语法本身,语法的标记由空白分隔,而空白不是标记,只是语法中的缩进很重要,这与程序中的缩进不同。
回答补遗的补遗
实际上,const
和volatile
需要用空格分隔并不是真的。它们只需要是单独的令牌。示例:
#define A(x)x
A(const)A(volatile)A(int)A(x)A(;)
同样,更重要的是,第2章(特别参考2.2和2.5,但您必须阅读全文)描述了如何处理程序文本以生成令牌流。所有声称必须忽略空白的规则都在语法的这一部分中,而所有声称可能需要空白的规则则不是。
这实际上是两种独立的语法,但词法语法必然是不完整的,因为你需要考虑预处理器的操作才能应用它
我相信我所说的每一句话都可以从标准中得到。以下是一些摘录:
2.2(3)源文件被分解为预处理标记(2.5)和空白字符序列(包括注释)…将源文件的字符划分为预处理令牌的过程取决于上下文。
…
2.2(7)分隔标记的空白字符不再有效。每个预处理令牌都被转换为一个令牌。(2.7)对生成的令牌进行语法和语义分析,并将其翻译为翻译单元。
我认为所有这些都清楚地表明,有两种语法,一种是词法语法,也就是说,它从一系列字形(字符)中产生一个字形(标记),另一种是句法语法,也即,它从一连串的字形(符号)中产生抽象语法树。在这两种情况下(除了一个小的例外,我稍后会讨论),如果词汇语法允许的话,除了阻止两个词元相互碰撞的东西之外,空白不被认为是其他任何东西。(参见2.5(3)中的算法。)
C++
在语法上并不漂亮,所以几乎总是有例外。其中一个继承自C
,是之间的区别
#define A(X)(X)
和
#define A (X)(X)
预处理指令有自己的解析规则,这一规则由以下定义代表:
lparen:
前面不是空白的(
字符
我想说,这是证明规则的例外[注1]。事实上,有必要说这个(
前面没有空白,这表明在句法规则中标记(
的正常使用并不能说明它的空白上下文。
因此,借用雷·卡明斯(而不是阿尔伯特·爱因斯坦,正如有时所说的那样)的话,";时间和空白都是将一个令牌与另一个令牌分隔开的"[注2]
[注1]我在这里使用了这个短语的原始法律意义,就像perCicero一样。
[注2]:
"时间,";乔治说:";为什么我可以给你一个时间的定义。这就是阻止一切同时发生的原因">
那一小群人发出阵阵笑声。
"确实如此;化学家同意了"先生们,这并不像听起来那么好笑。事实上,这确实是一个不错的科学定义。时间和空间将一个事件与另一个事件分隔开来…
摘自《掌握时间的人》,雷·卡明斯著,1929年,王牌图书。见第一页,在谷歌图书
标准实际上有两个独立的语法。
第2节和第16节中描述的预处理器语法定义了在翻译阶段1-6中如何将源字符序列转换为预处理标记和空白字符序列。在这个语法的某些阶段和部分中,空白是非常重要的。
在翻译阶段4之后,不属于预处理标记的空白字符不再重要。该标准在翻译阶段7开始时明确规定,在预处理标记之间丢弃空白字符。
语言语法定义了在翻译阶段7中如何对一系列标记(从预处理标记转换而来)进行语法和语义解释。在这个语法中没有空白这回事。(此时,' '
是字符文字,就像'c'
一样。)
在这两种语法中,标准中可见的语法组件之间的空间与源或执行空白字符无关,它只是为了使标准清晰可见。当预处理器语法依赖于空白时,它会用单词拼写出来,例如:
c-char:
源字符集的任何成员,单引号
'
、反斜杠或换行符除外
转义序列
通用字符名
和
控制线:
# define
标识符lparen标识符列表[opt])
替换列表换行lparen:
不紧跟空白的
(
字符
因此,整数文本的数字之间可能没有空格,因为预处理器语法不允许。
这里的另一个重要规则来自C++11 2.5p3:
如果输入流已被解析为预处理标记,最大可达给定字符:
如果下一个字符以一系列字符开头,这些字符可能是原始字符串文字的前缀和初始双引号,例如
R"
,则下一个预处理标记应为原始字符串文字。。。否则,如果接下来的三个字符是
<::
,并且随后的字符既不是:
也不是>
,则<
本身被视为预处理器令牌,而不是替代令牌<:
的第一个字符。否则,下一个预处理令牌是可能构成预处理令牌的最长字符序列,即使这会导致进一步的词汇分析失败。
因此,const
和volatile
令牌之间必须有空格,否则,最长的令牌规则将把它转换为单个标识符令牌constvolatile
。