几个月前我在D新闻组上发布了这个帖子,但出于某种原因,答案从未真正说服过我,所以我想我会在这里问。
The grammar of D is apparently context-free
The grammar of C++, however, isn't (even without macros).(请仔细阅读)
现在授予,我一无所知(正式)关于编译器,词法分析器和解析器。我所知道的只是我在网上学到的东西。
以下是(我相信)我对上下文的理解,在不那么技术性的术语中:
语言的语法是无上下文的当且仅当你总能理解给定的代码片段的含义(尽管不一定是确切的行为)而不需要“看”其他任何地方。
或者,甚至 less 严格:
如果我需要,语法不能没有上下文我只是通过查看就不能告诉表达式的类型。
因此,例如,C ++未通过无上下文测试,因为confusing<sizeof(x)>::q < 3 > (2)
的含义取决于{{1}的值 }
到目前为止,非常好。
现在我的问题是:D可以说同样的事情吗?
在D中,哈希表可以通过q
声明创建,例如
Value[Key]
静态数组可以用类似的语法定义:
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
当然不是 - 你不能将元组的哈希表作为键或值)。
我尝试为int[U]
创建的任何解析树都失败有意义(因为它需要知道节点是否包含数据类型与文字或标识符)除非延迟结果,直到Test
的值已知(使其依赖于上下文)。
我只是觉得我会评论:看到答案非常有趣,因为:
我不知道我是在学习还是变得更加困惑,但不管怎样,我很高兴我问过这个问题...感谢您抽出时间回答,大家好!
答案 0 :(得分:42)
无上下文首先是生成语法的属性。这意味着非终端可以生成的内容不依赖于非终端出现的上下文(在非上下文无关的生成语法中,“由给定的非终端生成的字符串”的概念通常很难界定)。这并不妨碍两个非终端生成相同的符号串(因此相同的符号串出现在具有不同含义的两个不同的上下文中)并且与类型检查无关。
如果至少有一个描述它的上下文无关语法,则通过声明语言是无上下文的,将无上下文定义从语法扩展到语言。
实际上,没有编程语言是无上下文的,因为“变量必须在使用之前必须声明”之类的东西无法通过无上下文语法检查(它们可以被其他一些语法检查) 。这还不错,实际上要检查的规则分为两部分:你要用语法检查的那些和你在语义传递中检查的规则(这种划分也允许更好的错误报告和恢复,所以你有时候我希望在语法中接受比为了给用户更好的诊断而更多的内容。
人们通过声明C ++不是无上下文的意思是,以一种方便的方式进行这种划分是不可能的(方便包括作为标准“几乎遵循官方语言描述”和“我的解析器生成器工具支持那种划分”;允许语法模糊,语义检查解决的歧义是一种相对简单的方法来完成C ++的切割,并遵循C ++标准,但它当你依赖不允许含糊不清的语法的工具时很不方便,当你有这样的工具时,很方便)。
我不太了解D,在语义检查的无上下文语法中是否有方便的语言规则削减,但你所展示的远不是证明没有的情况。
答案 1 :(得分:21)
无上下文的属性是一个非常正式的概念;你可以找到一个定义here。请注意,它适用于语法:如果存在至少一个识别它的上下文无关语法,则称该语言是无上下文的。请注意,可能存在识别相同语言的其他语法(可能不具有上下文)。
基本上它意味着语言元素的定义不能根据它周围的元素而改变。语言元素是指表达式和标识符等概念,而不是程序内部这些概念的特定实例,如a + b
或count
。
让我们尝试建立一个具体的例子。考虑一下这个简单的COBOL语句:
01 my-field PICTURE 9.9 VALUE 9.9.
这里我定义了一个字段,即一个变量,其大小适合保存一个整数位,小数点和一个十进制数字,初始值为9.9。一个非常不完整的语法可能是:
field-declaration ::= level-number identifier 'PICTURE' expression 'VALUE' expression '.'
expression ::= digit+ ( '.' digit+ )
不幸的是,PICTURE
后面的有效表达式与VALUE
后面的有效表达式不同。我可以在语法中重写第二个 production ,如下所示:
'PICTURE' expression ::= digit+ ( '.' digit+ ) | 'A'+ | 'X'+
'VALUE' expression ::= digit+ ( '.' digit+ )
这会使我的语法对上下文敏感,因为expression
根据它是在'PICTURE'
之后还是在'VALUE'
之后找到而不同。但是,正如已经指出的那样,这并没有说明底层语言。一个更好的选择是:
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)
这是不明确的,因为单独查看它无法判断这是函数模板调用还是布尔表达式。另一方面,前面的例子并不含糊不清;从语法的角度来看,它只能被解释为:
identifier assignment identifier binary-operator identifier semi-colon
在某些情况下,您可以通过在语法级别引入上下文敏感度来解决歧义。我不认为上面这个含糊不清的例子就是这种情况:在这种情况下,你不能在不知道a是否是模板的情况下消除歧义。请注意,当此类信息不可用时,例如,当它取决于特定的模板专业化时,该语言提供了解决歧义的方法:这就是为什么有时必须使用typename
来引用模板中的某些类型或者在调用成员函数模板时使用template
。
答案 2 :(得分:9)
答案 3 :(得分:8)
如果我需要,语法不能无上下文我无法分辨出类型 只是通过观察它来表达。
不,那是错的。如果你只是通过查看它和解析器的当前状态(我在函数中,在命名空间中等)来判断是一个表达式,那么语法就不能没有上下文了。
然而,表达式的类型是语义,而不是语法,并且解析器和语法不会给出类型或语义有效性的一分钱,或者您是否可以将元组作为哈希映射中的值或键,或者如果您在使用之前定义了该标识符。
语法不关心它意味着什么,或者是否有意义。它只关心是什么。
答案 4 :(得分:7)
D的词法分析器中有一个结构:
string ::= q" Delim1 Chars newline Delim2 "
其中Delim1和Delim2匹配标识符,而Chars不包含换行符Delim2。
这个构造是上下文敏感的,因此D的词法分析器语法是上下文敏感的。
自从我使用D语法以来已经有好几年了,所以我不记得头顶上的所有麻烦点,或者即使其中任何一个让D的解析器语法上下文敏感,但是我相信他们没有。从回想起,我会说D的语法是无上下文的,而不是任何k的LL(k),它有一个令人讨厌的模糊性。
答案 5 :(得分:6)
要回答编程语言是否无上下文的问题,您必须首先决定在语法和语义之间划线的位置。作为一个极端的例子,在C中,一个程序在被允许溢出后使用某些整数的值是非法的。显然,这在编译时无法检查,更不用说解析时间了:
void Fn() {
int i = INT_MAX;
FnThatMightNotReturn(); // halting problem?
i++;
if(Test(i)) printf("Weeee!\n");
}
作为其他人指出的一个不那么极端的例子,使用规则之前的减速无法在无上下文语法中强制执行,因此如果您希望保持语法传递上下文无关,那么必须将其推迟到下一次传递。
作为一个实际的定义,我将从以下问题开始:您是否可以使用无上下文语法正确无误地确定所有正确程序的解析树,并且对于所有不正确的程序(该语言需要被拒绝) ),要么拒绝它们在语法上无效,要么产生一个解析树,后来的传递可以识别为无效并拒绝?
鉴于D语法的最正确规范是解析器(IIRC是一个LL解析器),我强烈怀疑它实际上是上下文中我所建议的定义。
注意:上面没有说明语言文档或给定解析器使用什么语法,只有在存在无上下文语法的情况下才会这样做。此外,关于D语言的唯一完整文档是编译器DMD的源代码。
答案 6 :(得分:4)
这些答案让我头疼。
首先,低级语言的复杂性以及确定它们是否是无上下文的,是你编写的语言经常在很多步骤中处理。
在C ++中(订单可能已关闭,但这不应使我的观点无效):
因为第一步可以改变第二步的上下文而第二步可以改变第三步的上下文,所以你写的语言(包括所有这些步骤)都是上下文敏感的。
人们试图捍卫语言(说明它是无上下文的)的原因是,因为添加上下文的唯一例外是可跟踪的预处理器语句和模板调用。您只需要遵循规则的两个受限制的例外来假装语言没有上下文。
大多数语言总体上是上下文敏感的,但大多数语言只有这些次要的例外情况才能无上下文。