D的语法真的没有语境吗?

Meh*_*dad 59 c++ grammar d context-free-grammar

我几个月前在D新闻组上发布了这个,但由于某种原因,答案从未真正说服过我,所以我想我会在这里问.


D的语法显然没有上下文.

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

现在被授予,对编译器,词法分析器和解析器一无所知(正式).我所知道的只是我在网上学到的东西.
以下是(我相信)我对上下文的理解,在非技术术语中:

语言的语法是无上下文的,当且仅当你能够总是理解给定代码片段的含义(尽管不一定是确切的行为)而不需要在任何其他地方"看".

或者,严格:

如果我需要,语法不能无上下文我只是通过查看它就无法告诉表达式的类型.

因此,例如,C++失败上下文测试,因为意义confusing<sizeof(x)>::q < 3 > (2) 依赖于q.

到现在为止还挺好.

现在我的问题是:可以对D说同样的话吗?

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

int[string] peoplesAges;   // Maps names to ages
Run Code Online (Sandbox Code Playgroud)

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

int[3] ages;   // Array of 3 elements
Run Code Online (Sandbox Code Playgroud)

模板可以用来使它们混淆:

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.
Run Code Online (Sandbox Code Playgroud)

这意味着我无法分辨它的意思T[0]U只是通过观察它(即它可能是一个数字,它可能是一种数据类型,或者它可能是一个神知道什么的元组).我甚至无法判断表达式是否在语法上有效(因为int[U]当然不是 - 你不能将元组作为键或值的哈希表).

我试图制作的任何解析树Test没有任何意义(因为它需要知道节点是否包含数据类型而不是文字或标识符),除非它将结果延迟T已知值(使其成为已知)依赖于上下文的).

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

为什么/为什么不呢?


更新:

我只是想我会发表评论:看到答案非常有趣,因为:

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

我不知道我是在学习还是变得更加困惑,但无论哪种方式,我都很高兴我问过这个问题...感谢您花时间回答,大家好!

APr*_*mer 42

无上下文首先是生成语法的属性.这意味着非终端可以生成的内容将不依赖于非终端出现的上下文(在非上下文无关的生成语法中,"由给定的非终端生成的字符串"的概念通常很难界定).这并不妨碍两个非终端生成相同的符号串(因此相同的符号串出现在具有不同含义的两个不同的上下文中)并且与类型检查无关.

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

在实践中,没有编程语言是无上下文的,因为诸如"变量必须在使用之前声明"之类的东西不能通过无上下文语法来检查(它们可以被其他类型的语法检查).这还不错,实际上要检查的规则分为两部分:你要用语法检查的那些和你在语义传递中检查的规则(这种划分也允许更好的错误报告和恢复,所以你有时候想要接受更多的语法,而不是为了给用户提供更好的诊断能力.

人们通过声明C++不是无上下文的意思是,以一种方便的方式进行这种划分是不可能的(方便地包括作为标准"遵循几乎官方语言描述"和"我的解析器生成器工具支持那种划分";允许语法含糊不清,语义检查要解决的歧义是一种相对简单的方法来完成C++的切割,并遵循C++标准,但是当你依赖于工具时这很不方便不允许含糊不清的语法,当你有这样的工具时,很方便).

我不太了解D,在语义检查的无上下文语法中是否有方便的语言规则切割,但你所展示的并不是证明没有的情况.

  • 语法是一种描述终端串的方法(终端组是字母表).生成语法是你熟悉的那种语法.我的一点是,词汇,句法和语义之间的区别很大程度上归功于任意,并且主要是由于易于描述和工具的可用性.我的第二个要点是,无语境有一个正式的语法定义,并没有准确地说明你准备在词汇和语义阶段推进什么,你是在"你知道我的意思"领域. (10认同)
  • +1表示纯语法检查和语义检查之间的划分是一种有点随意的选择. (4认同)
  • 虽然这个答案是正确的,但有些人可能会觉得它很混乱,因为AProgrammer使用Chomsky的语言,其中语法"生成"一个字符串(字符串由语法生成)而不是编译器中*解析*的更自然的概念我们不关心生成字符串,我们只关心解释字符串的逆过程. (4认同)

Nic*_*tti 21

无上下文的属性是一个非常正式的概念; 你可以在这里找到一个定义.请注意,它适用于语法:如果存在至少一个识别它的上下文无关语法,则称该语言是无上下文的.请注意,可能存在识别相同语言的其他语法,可能不是上下文.

基本上它意味着语言元素的定义不能根据它周围的元素而改变.语言元素我指的是表达式标识符等概念,而不是程序内部这些概念的特定实例,如a + bcount.

让我们试着建立一个具体的例子.考虑一下这个简单的COBOL语句:

   01 my-field PICTURE 9.9 VALUE 9.9.
Run Code Online (Sandbox Code Playgroud)

这里我定义了一个字段,即一个变量,其大小设置为保存一个整数位,小数点和一个十进制数字,初始值为9.9.一个非常不完整的语法可能是:

field-declaration ::= level-number identifier 'PICTURE' expression 'VALUE' expression '.'
expression ::= digit+ ( '.' digit+ )
Run Code Online (Sandbox Code Playgroud)

不幸的是,可以遵循的有效表达式与可以遵循PICTURE的有效表达式不同VALUE.我可以用我的语法重写第二个作品如下:

'PICTURE' expression ::= digit+ ( '.' digit+ ) | 'A'+ | 'X'+
'VALUE' expression ::= digit+ ( '.' digit+ )
Run Code Online (Sandbox Code Playgroud)

这将使我的语法上下文敏感,因为expression根据它是在之后'PICTURE'还是之后被发现而将是不同的'VALUE'.但是,正如已经指出的那样,这并没有说明底层语言.一个更好的选择是:

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

这是无背景的.

如您所见,这与您的理解截然不同.考虑:

a = b + c;
Run Code Online (Sandbox Code Playgroud)

在没有查找a,b和c的声明的情况下,你可以对这个声明说几乎没有,这是有效声明的任何语言,但这本身并不意味着任何这些语言都不是没有上下文.可能令你感到困惑的是,背景自由与模糊性不同.这是C++示例的简化版本:

a < b > (c)
Run Code Online (Sandbox Code Playgroud)

这是不明确的,因为单独查看它无法判断这是函数模板调用还是布尔表达式.另一方面,前面的例子并不含糊不清; 从语法的角度来看,它只能被解释为:

identifier assignment identifier binary-operator identifier semi-colon
Run Code Online (Sandbox Code Playgroud)

在某些情况下,您可以通过在语法级别引入上下文敏感度来解决歧义.我不认为上面这个含糊不清的例子就是这种情况:在这种情况下,你不能在不知道a是否是模板的情况下消除歧义.请注意,当此类信息不可用时,例如,当它取决于特定的模板专业化时,该语言提供了解决歧义的方法:这就是您有时必须使用typename模板中的某些类型或template在调用成员时使用的原因功能模板.

  • 您的COBOL示例有误.这在无上下文语法中是完全合法的.仅仅因为语法元素可以有多种解释,这并不意味着语言是上下文敏感的.查看定义或阅读其他一些答案以获取更多详细信息. (2认同)
  • @Mehrdad:一个无上下文的语法确实可能是模糊的,因为像`int [T [0]]`这样的字符串可以用多种方式解析.您可以在不查看上下文的情况下确定它是有效的语法,而不是它的含义.在上下文敏感的语言中,甚至这种句法元素的有效性也取决于它的上下文. (2认同)

Sha*_*baz 9

已经有很多好的答案,但由于你不了解语法,解析器和编译器等,让我通过一个例子来证明这一点.

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

S -> a T
T -> b G t
T -> Y d
b G -> a Y b
Y -> c
Y -> lambda (nothing)
Run Code Online (Sandbox Code Playgroud)

并想象一下你S.大写字母是非终端的,小写字母是终端.这意味着如果你得到所有终端的句子,你可以说语法生成该句子作为语言中的"单词".想象一下用上面的语法进行这样的替换(*phrase*之间的短语是被替换的那个):

*S* -> a *T* -> a *b G* t -> a a *Y* b t -> a a b t
Run Code Online (Sandbox Code Playgroud)

所以,我可以aabt用这个语法创建.

好的,回到主线.

让我们假设一种简单的语言.你有数字,两种类型(int和string)和变量.你可以对整数进行乘法运算,对字符串进行加法,但不能相反.

你需要的第一件事是词法分析器.这通常是与程序令牌匹配的常规语法(或等于它,DFA,或同样是正则表达式).在正则表达式中表达它们是很常见的.在我们的例子中:

(我正在制作这些语法)

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 a-z or A-Z or _
                                  // then as many a-z or A-Z 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
Run Code Online (Sandbox Code Playgroud)

所以,现在你有一个常规语法,标记你的输入,但它不了解结构.

然后你需要一个解析器.解析器通常是无上下文语法.无上下文语法意味着,在语法中,语法规则的左侧只有单个非终结符.在本答案开头的例子中,规则

b G -> a Y b
Run Code Online (Sandbox Code Playgroud)

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

好吧,当你写一个语法时,每个非终结符都有意义.让我们为我们的例子写一个无上下文的语法(|表示或者.就像在同一行中编写许多规则一样):

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
Run Code Online (Sandbox Code Playgroud)

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

x = 1*y
int x
string y
z = x+y
Run Code Online (Sandbox Code Playgroud)

在语法上,这段代码是正确的.那么,让我们回到无环境意味着什么.正如您在上面的示例中所看到的,当您展开时executable,您生成一个表单语句,variable = operand operator operand而不考虑您所在的代码部分.无论是开头还是中间,变量是否定义,或者类型是否匹配,你都不知道而且你不在乎.

接下来,您需要语义.这是上下文敏感的语法发挥作用.首先,让我告诉你,实际上,没有人真正编写上下文敏感语法(因为解析它太难了),而是解析器在解析输入时调用的一些代码片段(称为动作例程.虽然这不是唯一的办法).但是,正式地,您可以定义所需的一切.例如,要确保在使用变量之前定义变量,而不是此变量

executable -> variable equal expression
Run Code Online (Sandbox Code Playgroud)

你必须有这样的东西:

declaration some_code executable -> declaration some_code variable equal expression
Run Code Online (Sandbox Code Playgroud)

但更复杂的是,确保variablein声明与正在计算的声明匹配.

无论如何,我只想给你一个想法.所以,所有这些都是上下文相关的:

  • 类型检查
  • 功能的参数数量
  • 功能的默认值
  • 如果member存在obj于代码中:obj.member
  • 几乎任何不喜欢的东西:缺失;}

我希望你知道有什么不同(如果你没有,我会非常乐意解释).

总结如下:

  • Lexer使用常规语法来标记输入
  • Parser使用无上下文语法来确保程序的结构正确
  • 语义分析器使用上下文敏感语法进行类型检查,参数匹配等

但不一定总是这样.这只是向您展示了每个级别如何变得更强大以便能够执行更多操作.但是,每个提到的编译器级别实际上可能更强大.

例如,我不记得的一种语言,使用数组订阅和函数调用括号,因此它需要解析器查找变量的类型(上下文相关的东西)并确定哪个规则(function_call或array_substitution)取.

如果您使用具有重叠的正则表达式的词法分析器设计语言,那么您还需要查找上下文以确定要匹配的令牌类型.

来回答你的问题!通过您提到的示例,很明显c ++语法不是无上下文的.语言D,我完全不知道,但你现在应该能够解释它.可以这样想:在无上下文语法中,非终结符可以扩展而不考虑任何东西,但语言的结构.与你所说的相似,它会扩展,而不会在任何其他地方"寻找".

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

sentence -> subject verb object clause
clause -> .... | lambda
Run Code Online (Sandbox Code Playgroud)

好了,sentenceclause这里是终结符号.有了这个语法,你可以创建这些句子:

I go there because I want to
Run Code Online (Sandbox Code Playgroud)

要么

I jump you that I is air
Run Code Online (Sandbox Code Playgroud)

如您所见,第二个具有正确的结构,但没有意义.只要涉及无上下文语法,其含义无关紧要.它只是扩展verb到任何动词而不"看"句子的其余部分.

因此,如果您认为D必须在某个时刻检查某些内容在其他地方的定义,只是说该程序在结构上是正确的,那么它的语法就不是无上下文的.如果你隔离代码的任何部分,它仍然可以说它在结构上是正确的,那么它是无上下文的.

  • @Dariooo,它甚至可以是1行,如果你用数学方法对它进行测试,但大多数人仍需要解释才能理解.;)感谢upvote (2认同)

Pup*_*ppy 8

如果我需要,语法不能无上下文我只是通过查看它就无法告诉表达式的类型.

不,那是错的.如果你只是通过查看它和解析器的当前状态(我在函数中,在命名空间中等)来判断它是否是表达式,那么语法就不能没有上下文了.

然而,表达式的类型是语义,而不是语法,解析器和语法不会给出类型或语义有效性的一分钱,或者你是否可以将元组作为哈希映射中的值或键,或者如果你在使用它之前定义了该标识符.

语法不关心它意味着什么,或者是否有意义.它只关心它什么.

  • state == context (2认同)

Ell*_*mer 7

D'lexer中有一个结构:

string ::= q" Delim1 Chars newline Delim2 "
Run Code Online (Sandbox Code Playgroud)

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

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

自从我使用D语法以来已经有好几年了,所以我不记得我脑子里的所有麻烦点,或者即使其中任何一个让D的解析器语法上下文敏感,但我相信他们这样做了不.从回忆中,我会说D的语法是无上下文的,而不是任何k的LL(k),并且它具有令人讨厌的模糊性.


BCS*_*BCS 6

要回答编程语言是否无上下文的问题,您必须首先确定在语法和语义之间划分界限的位置.作为一个极端的例子,在C中,一个程序在被允许溢出后使用某些整数的值是非法的.很明显,这在编译时无法检查,更不用说解析时间了:

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

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

作为一个实际的定义,我将从以下问题开始:你能否正确无误地使用无上下文语法确定所有正确程序的解析树,并且对于所有不正确的程序(语言需要被拒绝),要么拒绝它们语法无效或产生一个解析树,后来的传递可以识别为无效并拒绝?

鉴于D语法的最正确的规范是解析器(IIRC是LL解析器),我强烈怀疑它实际上是由我建议的定义上下文.


注意:上面没有说明语言文档或给定解析器使用的语法,只有在存在无上下文语法的情况下.此外,关于D语言的唯一完整文档是编译器DMD的源代码.