D的语法真的是上下文吗?

我已经在几个月前在D新闻组上发布了这个消息,但是由于某种原因,答案从来没有真正让我信服,所以我想我会在这里问。


D的语法显然是上下文无关的 。

但是,C ++的语法不是(即使没有macros)。 ( 请仔细阅读!

现在被授予, 对编译器,词法分析器和parsing器一无所知 (正式)。 我所知道的是从我在网上学到的东西。
以下是我所了解的关于上下文的一些(我相信),用不太专业的术语:

语言的语法是上下文无关的, 当且仅当你总是可以理解给定代码段的含义(尽pipe不一定是确切的行为),而不需要在其他地方“看”。

或者,更严格的是:

如果我需要的话,语法不能是上下文无关的,我只是通过查看它不能告诉expression式的types。

因此,例如,C ++由于confusing<sizeof(x)>::q < 3 > (2)含义 取决于q因此上下文无关testing失败。

到现在为止还挺好。

现在我的问题是:D可以这样说吗?

在D中,哈希表可以通过Value[Key]声明创build,例如

 int[string] peoplesAges; // Maps names to ages 

静态数组可以用类似的语法来定义:

 int[3] ages; // Array of 3 elements 

和模板可以用来使他们感到困惑:

 template Test1(T...) { alias int[T[0]] Test; } template Test2(U...) { alias int[U] Test2; // LGTM } Test1!(5) foo; Test1!(int) bar; Test2!(int) baz; // Guess what? It's invalid code. 

这意味着我不能仅仅通过查看T[0]或者U而告诉它的意义 (即它可能是一个数字,它可能是一个数据types,或者它可能是一个上帝知道的元组)。 我什至不能告诉expression式是否语法有效(因为int[U]当然不是 – 你不能有一个哈希表元组作为键或值)。

任何我试图用于Testparsing树都不会有任何意义(因为它需要知道节点是否包含数据types与文字或标识符),除非它延迟结果直到T的值已知(使其与上下文相关)。

鉴于此,D实际上是无上下文的,还是我误解了这个概念?

为什么/为什么不呢?


更新:

我只是想我会评论:看到答案真的很有趣,因为:

  • 一些答案声称C ++和D 不能无上下文
  • 一些答案声称C ++和D都是上下文无关的
  • 有些答案支持C ++是上下文敏感的,而D则不是
  • 还没有人声称C ++是上下文无关的,而D是上下文敏感的:-)

我不知道我是否正在学习或者变得更加困惑,但是无论如何,我很高兴我问了这个问题……谢谢你花时间回答大家!

没有语境是首先生成语法的一个属性。 这意味着非terminal可以产生的东西不取决于非terminal出现的环境(在非上下文无关的生成语法中,“由给定的非terminal生成的string”的概念通常是困难的界定)。 这并不妨碍两个非terminal产生相同的符号串(因此相同的符号串出现在具有不同含义的两个不同的上下文中)并且与types检查无关。

如果至less有一个描述它的上下文无关语法,那么通过声明一个语言是上下文无关的,将上下文无关的定义从语法扩展到语言是很常见的。

在实践中,没有一种编程语言是无上下文的,因为诸如“一个variables必须在使用之前被声明”这样的东西不能通过上下文无关的语法来检查(它们可以通过其他types的语法来检查)。 这并不坏,在实践中,要检查的规则分为两部分:那些你想检查语法和检查语义通行证(这个部门也允许更好的错误报告和恢复,所以你有时候想要接受更多的语法,而不是为了给用户提供更好的诊断)。

C ++没有上下文是什么意思,这样做是不可能的,方便的方式包括作为标准“几乎跟随官方语言描述”和“我的语法分析器生成器工具支持那种划分“,允许语法模糊,语义检查所解决的歧义是一种相对容易的方法,对C ++进行裁剪并遵循C ++标准,但是当你依赖于工具时,这是不方便的不允许含糊不清的语法,当你有这样的工具时,很方便)。

我对D的了解不够,不知道在语义检查中是否存在语境规则的方便的语法规则,但是你所展现的远不是certificate没有语言规则的情况。

上下文无关的性质是一个非常正式的概念, 你可以在这里find一个定义。 请注意,它适用于语法 :如果至less有一个可识别语言的上下文无关语法,则语言被认为是上下文无关的。 请注意,可能有其他语法,可能没有上下文空闲,识别相同的语言。

基本上这意味着一个语言元素的定义不能根据哪个元素包围它。 通过语言元素,我指的是像expression式标识符这样的概念,而不是像程序中的这些概念的具体实例,如a + bcount

我们试着去build立一个具体的例子。 考虑一下这个简单的COBOL语句:

  01 my-field PICTURE 9.9 VALUE 9.9. 

在这里,我定义了一个字段,即一个variables,它的大小可以保存一个整数,小数点和一个十进制数字,初始值为9.9。 一个非常不完整的语法可能是:

 field-declaration ::= level-number identifier 'PICTURE' expression 'VALUE' expression '.' expression ::= digit+ ( '.' digit+ ) 

不幸的是, PICTURE的有效expression式可能跟不上VALUE有效expression式。 我可以用下面的语法重写第二个语法:

 'PICTURE' expression ::= digit+ ( '.' digit+ ) | 'A'+ | 'X'+ 'VALUE' expression ::= digit+ ( '.' digit+ ) 

这将使我的语法上下文敏感,因为根据是在'PICTURE'之后还是在'VALUE'之后发现, expression将是不同的事情。 但是,正如已经指出的那样,这并没有提到底层语言的任何内容。 更好的select是:

 field-declaration ::= level-number identifier 'PICTURE' format 'VALUE' expression '.' format ::= digit+ ( '.' digit+ ) | 'A'+ | 'X'+ expression ::= digit+ ( '.' digit+ ) 

这是上下文无关的。

正如你所看到的,这与你的理解有很大的不同。 考虑:

 a = b + c; 

关于这个陈述,你几乎没有什么可以说的,没有用任何一种语言来查看a,b和c的声明,但是这本身并不意味着这些语言中的任何一种都不是上下文无关。 也许令你感到困惑的是情境自由与含糊不同的事实。 这是您的C ++示例的简化版本:

 a < b > (c) 

这是不明确的,通过独自看,你不能说这是一个函数模板调用或布尔expression式。 另一方面,前面的例子并不含糊, 从语法的angular度来看,它只能被解释为:

 identifier assignment identifier binary-operator identifier semi-colon 

在某些情况下,您可以通过在语法级别引入上下文敏感性来解决歧义。 我不认为这是上述模糊的例子:在这种情况下,你不能不知道是否是一个模板消除歧义。 请注意,当这些信息不可用时,例如,当它取决于特定的模板专业化时,该语言提供了解决歧义的方法:这就是为什么有时必须使用typename来引用模板中的某些types,或者在您使用template时调用成员函数模板。

如果我需要的话,语法不能是上下文无关的,我只是通过查看它不能告诉expression式的types。

不,这是完全错误的。 如果仅仅通过查看expression式和parsing器的当前状态(我在函数中,在命名空间中等等),就不能确定语法是否为上下文。

然而,expression式的types是一个语义含义,而不是句法,parsing器和语法不会给出关于types或语义有效性的一分钱,或者在hashps中是否可以有元组作为值或键,或者如果你在使用之前定义该标识符。

语法不关心它的意思,或者如果这是有道理的。 它只关心它什么。

已经有很多很好的答案了,但是由于你不了解语法,parsing器和编译器等,所以让我通过一个例子来演示。

首先,语法的概念非常直观。 想象一下一套规则:

 S -> a T T -> b G t T -> Y d b G -> a Y b Y -> c Y -> lambda (nothing) 

想象一下,你从S开始。 大写字母是非terminal,小写字母是terminal。 这意味着,如果你得到所有terminal的句子,你可以说语法生成该句子作为语言中的“单词”。 用上面的语法想象这样的replace(在* phrase *之间的短语是被replace的那个):

 *S* -> a *T* -> a *b G* t -> aa *Y* bt -> aabt 

所以,我可以用这个语法创build一个aabt

好的,回到主线。

让我们假设一个简单的语言。 你有数字,两种types(int和string)和variables。 你可以对整数和string进行乘法运算,而不是相反。

首先你需要的是一个词法分析器。 这通常是与程序令牌匹配的正则语法(或等于它,DFA或同样正则expression式)。 用正则expression式来expression它们是很常见的。 在我们的例子中:

(我正在使这些语法)

 number: [1-9][0-9]* // One digit from 1 to 9, followed by any number // of digits from 0-9 variable: [a-zA-Z_][a-zA-Z_0-9]* // You get the idea. First az or AZ or _ // then as many az or AZ or _ or 0-9 // this is similar to C int: 'i' 'n' 't' string: 's' 't' 'r' 'i' 'n' 'g' equal: '=' plus: '+' multiply: '*' whitespace: (' ' or '\n' or '\t' or '\r')* // to ignore this type of token 

所以,现在你得到了一个正规的语法,把你的input标记出来,但是它不了解结构。

那么你需要一个parsing器。 parsing器通常是一个上下文无关语法。 上下文无关语法意味着,在语法中,在语法规则的左侧只有单个非终结符。 在这个答案的开始的例子中,规则

 b G -> a Y b 

使语法上下文敏感,因为在左边你有b G而不仅仅是G 这是什么意思?

那么,当你写一个语法时,每个非终结者都有一个意义。 让我们为我们的例子编写一个上下文无关的语法(| means或者,就像在同一行写许多规则一样):

 program -> statement program | lambda statement -> declaration | executable declaration -> int variable | string variable executable -> variable equal expression expression -> integer_type | string_type integer_type -> variable multiply variable | variable multiply number | number multiply variable | number multiply number string_type -> variable plus variable 

现在这个语法可以接受这个代码:

 x = 1*y int x string y z = x+y 

在语法上,这个代码是正确的。 那么,让我们回到没有上下文的手段。 正如你在上面的例子中看到的那样,当你展开executable ,你会生成一个表单variable = operand operator operand语句,而不用考虑你所在代码的哪一部分。 无论是开始还是中间,variables是否被定义,或者types是否匹配,你都不知道,你也不在乎。

接下来,你需要语义。 这是上下文敏感的语法发挥作用。 首先让我告诉你,实际上,实际上没有人写上下文敏感的语法(因为parsing它太困难了),而是parsing器在parsinginput时调用的一小段代码(称为动作例程。虽然这不是唯一的办法)。 然而,forms上,你可以定义所有你需要的。 例如,要确保在使用它之前定义一个variables,而不是这个

 executable -> variable equal expression 

你必须有这样的东西:

 declaration some_code executable -> declaration some_code variable equal expression 

尽pipe如此,为了确保声明中的variable与正在计算的variable相匹配。

无论如何,我只是想给你这个想法。 所以,所有这些都是上下文敏感的:

  • types检查
  • 参数的数量
  • 默认值为function
  • 如果member存在obj中的代码: obj.member
  • 几乎所有不相似的东西:失踪;}

我希望你有一个想法是什么区别(如果你没有,我会更乐意解释)。

总之:

  • Lexer使用正则语法来标记input
  • parsing器使用上下文无关语法来确保程序结构正确
  • 语义分析器使用上下文敏感的语法来进行types检查,参数匹配等

虽然这并不总是这样。 这只是告诉你,每个级别如何变得更强大,能够做更多的东西。 但是,所提到的每个编译器级别实际上可能更强大。

例如,我不记得的一种语言,使用数组订阅和函数调用都带括号,因此它需要parsing器去查找variables的types(上下文相关的东西),并确定哪个规则(function_call或array_substitution)采取。

如果您使用词法分析器devise了一个正则expression式重叠的语言,那么您还需要查看上下文以确定您正在匹配哪种types的标记。

为了解决您的问题! 在你提到的例子中,显然c ++语法不是上下文的。 语言D,我绝对不知道,但你现在应该可以推论。 这样思考:在一个上下文无关的语法中,一个非终结符可以扩展而不考虑任何东西,但是这个语言的结构。 类似于你所说的,它扩大了,没有“看”任何其他地方。

一个熟悉的例子是自然语言。 例如用英文,你说:

 sentence -> subject verb object clause clause -> .... | lambda 

那么,这里的sentenceclause就是非终结者。 有了这个语法,你可以创build这些句子:

 I go there because I want to 

要么

 I jump you that I is air 

正如你所看到的,第二个结构正确,但没有意义。 只要一个上下文无关的语法,意义并不重要。 它只是将verb扩展到任何动词而不“看”句子的其余部分。

所以,如果你认为D在某种程度上要检查其他地方是如何定义的,就说程序在结构上是正确的,那么语法就不是上下文的。 如果你隔离了代码的任何部分,它仍然可以说它是结构上正确的,那么它是上下文无关的。

要回答编程语言是否是上下文无关的问题,您必须首先决定在语法和语义之间划界的地方。 作为一个极端的例子,C程序在被允许溢出之后使用某些整数的值是非法的。 显然这在编译时无法检查,更不用说parsing时间了:

 void Fn() { int i = INT_MAX; FnThatMightNotReturn(); // halting problem? i++; if(Test(i)) printf("Weeee!\n"); } 

作为其他人指出的一个不太极端的例子,使用规则之前的减速不能在上下文无关的语法中强制执行,所以如果您希望保持语法的上下文无关联,那么必须推迟到下一个遍。

作为一个实际的定义,我将从以下问题开始: 您能否正确无误地使用上下文无关语法确定所有正确程序的parsing树,并且对于所有不正确的程序(即语言要求被拒绝),要么将其拒绝为语法无效或产生一个后来通过的parsing树可以识别为无效和拒绝?

鉴于D语法的最正确的规范是parsing器(IIRC LL分析器),我强烈怀疑它实际上是由我提出的定义的上下文。


注意:只有在上下文无关语法存在的情况下,上述内容才说明语言文档或给定parsing器使用什么语法。 此外,D语言的唯一完整文档是编译器DMD的源代码。

D的词法分析器中有一个构造:

 string ::= q" Delim1 Chars newline Delim2 " 

Delim1和Delim2是匹配的标识符,而Chars不包含换行符Delim2。

这个构造是上下文敏感的,因此D的词法分析器语法是上下文敏感的。

我已经用了D语法很多年了,所以我不记得我头脑中所有的麻烦点,或者即使它们中的任何一个都使得D的parsing器语法上下文敏感,但我相信它们确实不。 从回忆中,我会说德语的语法是上下文无关的,而不是任何k的LL(k),它具有令人讨厌的模糊性。

这些答案让我头痛。

首先,低级别语言的复杂性以及是否上下文无关,是因为您写入的语言通常经过多个步骤处理。

在C + + (顺序可能会closures,但不应该使我的观点无效):

  1. 它必须处理macros和其他预处理器的东西
  2. 它必须解释模板
  3. 它最终解释你的代码。

因为第一步可以改变第二步的上下文,第二步可以改变第三步的上下文,所以你写的语言(包括所有这些步骤)是上下文敏感的。

人们试图捍卫一种语言(表明它是上下文无关的)的原因是,因为唯一添加上下文的exception是可跟踪的预处理器语句和模板调用。 你只需要遵守规则的两个有限的例外来假装语言是上下文无关的。

大多数语言都是上下文相关的,但大多数语言只有上下文无关的小例外。