10

冒着提出一个被认为过于挑剔的问题的风险,我花了很长时间试图证明(作为在整个标准中在不同情况下发生的事情的一个例子)以下第integer literal2.14.2 节中的an 定义C++11 标准,特别是关于一个细节,即语法符号本身中空格的存在。

(请注意,这个例子 - 整数文字的定义 - 不是我的问题的重点。我的问题的重点是询问 C++ 标准本身使用的语法描述符号,特别是关于语法类别名称之间的空格. 我在这里给出的例子——整数文字的定义——是专门选择的,只是因为它作为一个简单明了的例子。)

(为简洁起见,来自 §2.14.2):

integer-literal:
    decimal-literal integer-suffix_opt

decimal-literal:
    nonzero-digit
    decimal-literal digit

(正如预期的那样,[0] 1 ... 9)nonzero-digitdigit(注:以上文字在标准中均为斜体。)

这一切对我来说都是有意义的,假设语法类别描述之间的空格decimal-literaldigit理解为不存在于实际源代码中,但仅存在于语法描述本身中,因为它出现在第 2.14.2 节中。

这个约定——在符号中的类别描述之间放置一个空格,可以理解该空格不会出现在源代码中——在规范的其他地方使用。这里的例子只是一个明确的例子,其中空间显然不应该出现在源代码中。(请参阅此问题的附录,了解标准中的反例,其中当这些类别描述被源代码中的实际标记替换时,类别描述之间必须存在或可选的空格或其他分隔符。)

同样,冒着吹毛求疵的风险,我无法在标准中的任何地方找到一个约定声明,即在解释本示例中的符号时,源代码中不存在空格。

该标准在第 1.6.1 节(及之后)中讨论了符号约定。我能找到的唯一相关文本是:

在本国际标准使用的句法符号中,句法类别用斜体表示,字面量和字符用等宽类型表示。替代品列在单独的行中,除非在少数情况下,一长串替代品用短语“一个”标记。</p>

我不会那么挑剔;然而,我发现标准中使用的符号有点棘手,所以我想清楚所有细节。我很感激任何愿意花时间让我了解这一点的人。

附录 针对声称类似于“很明显不应在最终源代码中包含空格,因此标准没有必要明确说明这一点”的评论:我在这个问题中选择了一个简单的例子,明显的地方。标准中有很多情况下,如果没有 a就不明显了。语言的先验知识(在我看来),例如 §8.0.4 讨论“const”和“volatile”:

cv-qualifier-seq:
    cv-qualifier cv-qualifier-seq_opt

...请注意此处的相反假设(最终源代码中需要空格或另一个或多个分隔符),但这不可能从语法符号本身推断出来。

在某些情况下,空格是可选的,例如:

noptr-abstract-declarator:
    noptr-abstract-declarator_opt parameters-and-qualifiers

(在这个例子中,为了说明一点,我不会给出节号或解释正在讨论的内容;我只会问从语法符号本身来看,在这种情况下,最终源代码中的空格是否很明显在标记之间是可选的。)

我怀疑这些评论——“很明显,所以它必须是这样”——是因为我选择的例子是如此明显。这正是我选择这个例子的原因。

4

4 回答 4

8

§2.7.1

有五种标记:标识符、关键字、文字、运算符和其他分隔符。如下所述的空白、水平和垂直制表符、换行符、换页符和注释(统称为“空白”)将被忽略,除非它们用于分隔标记

因此,如果文字是一个标记,并且空格用于分隔标记,则文字数字之间的空格将被解释​​为两个单独的标记,因此不能是同一文字的一部分。

于 2012-12-07T00:08:20.003 回答
6

我有理由确定标准中没有对这一事实更直接的解释。

使用的符号与典型的 BNF 足够相似,以至于它们认为许多相同的一般约定是理所当然的,包括符号中的空格除了分隔 BNF 本身的标记之外没有任何意义——如果/当空格在除了分隔标记之外的源代码,它们将包含直接指定它的符号(例如,对于大多数预处理指令,new-line直接指定:

# ifdef标识符换行组选择

或者:

# 包含<h-char-sequence> 换行符

可能要归咎于Algol 68 标准,该标准在精确指定语法方面做得太过分了,以至于如果没有数周的全日制学习1 ,基本上任何人都无法阅读。从那时起,除了对语法描述语言的最粗略的解释之外,任何超过最粗略的解释都会导致拒绝,因为它太像 Algol 68 并且无疑会失败,因为它太正式并且没有人会阅读或理解它。


1你问的怎么会这么糟糕?它基本上是这样的:他们从语法描述语言的正式英语描述开始。不过,这并不是用来定义 Algol 68 的——它被用来指定(更准确地说)另一种语法描述语言。然后使用第二种语法描述语言来指定 Algol 68 本身的语法。因此,您必须先学习两种不同的语法描述语言,然后才能开始阅读 Algol 68 语法本身。你可以毫无疑问地猜到,几乎没有人做过。

于 2012-12-07T05:30:46.173 回答
3

该标准实际上有两个独立的语法。

第 2 节和第 16 节中描述的预处理器语法定义了在翻译阶段 1-6 中如何将源字符序列转换为预处理标记和空白字符序列。在该语法的某些阶段和部分中,空格很重要。

不属于预处理标记的空白字符在翻译阶段 4 后不再重要。标准在翻译阶段 7 开始时明确规定要丢弃预处理标记之间的空白字符。

语言语法定义了一系列标记(从预处理标记转换而来)在翻译阶段 7 中如何在句法和语义上进行解释。该语法中没有空格之类的东西。(此时,' 'is就像is 一样是字符文字'c'。)

在这两种语法中,标准中可见的语法组件之间的空格与源或执行空白字符无关,它只是为了使标准清晰易读。当预处理器语法依赖于空格时,它会用单词拼写出来,例如:

c字符

源字符集的任何成员,除了单引号'、反斜杠\或换行符

转义序列

通用字符名称

控制线

...

# define identifier lparen identifier-list [opt]) 替换列表换行符

...

lparen

(前面没有空格的字符

所以整数文字的数字之间可能没有空格,因为预处理器语法不允许它。

这里的另一个重要规则来自 C++11 2.5p3:

如果输入流已被解析为预处理标记,直到给定字符:

  • 如果下一个字符开始可能是原始字符串文字的前缀和初始双引号的字符序列,例如R",则下一个预处理标记应为原始字符串文字。...

  • 否则,如果接下来的三个字符是<::且后续字符既不是:>,则<本身被视为预处理器标记,而不是替代标记的第一个字符<:

  • 否则,下一个预处理标记是可以构成预处理标记的最长字符序列,即使这会导致进一步的词法分析失败。

const所以和标记之间必须有空格,volatile否则,最长标记可能规则会将其转换为单个标识符token constvolatile

于 2012-12-07T19:09:28.590 回答
3

正如你所说,标准说:

等宽类型的字面量和字符

因此,如果要在规则中包含文字空间,则必须以恒定宽度类型呈现。仔细检查标准会发现,您所指的生产空间比等宽类型要窄。(此外,您引用该标准的尝试是一种歪曲,因为它以恒定宽度类型呈现应该以斜体呈现的内容,随之而来的语义发生变化。)


好的,这就是“有抱负的语言律师”的答案;此外,它并没有真正起作用,因为它在所有形式的产品上都失败了:

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) 和空白字符序列(包括注释)…… 将源文件的字符划分为预处理标记的过程取决于上下文。

…</p>

2.2(7) 分隔标记的空白字符不再重要。每个预处理令牌都被转换为一个令牌。(2.7)。生成的标记在句法和语义上进行分析,并作为翻译单元进行翻译。

我认为所有这一切都清楚地表明有两种语法,一种是词法的——也就是说,它从一系列字素(字符)中产生一个词位(标记)——而另一种是句法的——也就是说,它产生一个来自一系列词位(令牌)的抽象语法树。在这两种情况下(有一个小例外,我将在一分钟内讨论)除了词汇语法允许的情况下阻止两个词位相互碰撞的东西之外,空格被认为是其他任何东西。(参见 2.5(3) 中的算法。)

C++在语法上并不漂亮,所以几乎总是有例外。其中之一,继承自C,是以下之间的区别:

#define A(X)(X)

#define A (X)(X)

预处理指令有自己的解析规则,这一规则以定义为代表:

lparen :前面没有空格
  的(字符

我想说,这是证明规则的例外 [注 1]。有必要说这(前面没有空格这一事实表明,(在句法规则中正常使用标记并没有说明它的 blancospatial 上下文。

因此,套用雷·卡明斯(不是有时声称的阿尔伯特·爱因斯坦)的话说,“时间和空白都是将一个标记与另一个标记分开的全部。” [笔记2]


[注 1] 根据Cicero的说法,我在此处使用该短语的原始法律意义。

[笔记2]:

“时间,”乔治说,“为什么我可以给你一个时间的定义。它可以阻止一切同时发生。”

一小群人传来一阵笑声。

“的确如此,”化学家同意道。“而且,先生们,这并不像听起来那么好笑。事实上,这确实不是一个糟糕的科学定义。时间和空间就是将一个事件与另一个事件分开的全部......</p>

——选自《掌握时间的人》,Ray Cummings 着,1929 年,Ace Books。在 Google 图书中查看首页

于 2012-12-07T06:28:45.610 回答