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

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


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

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

现在被授予, 对编译器,词法分析器和解析器一无所知 (正式)。 我所知道的仅仅是从我在网上学到的知识。
这就是我所了解的关于上下文的内容(我相信),用非技术性的术语来解释:

语言的语法是上下文无关的,当且仅当您始终能够理解给定代码段的含义(尽管不一定是确切的行为)而无需在其他地方“看”。

或者,更不严格:

如果我需要的话,语法不能没有上下文,只是通过查看它就无法判断表达式的类型。

因此,例如,C ++由于confusing<sizeof(x)>::q < 3 > (2)含义 取决于q的值,因此无法进行上下文无关测试。

到现在为止还挺好。

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

例如,在D中,哈希表可以通过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创建的任何解析树都不会有任何意义(因为它需要知道节点是否包含数据类型与文字或标识符),除非它延迟结果直到T的值已知(使其与上下文相关)。

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

为什么/为什么不?


更新:

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

  • 一些答案声称C ++和D不能无上下文
  • 一些答案声称C ++和D都是上下文无关的
  • 一些答案支持C ++是上下文敏感而D不是的说法
  • 还没有人声称C ++是上下文无关的,而D是上下文敏感的:-)
  • 我不知道我是在学习还是变得更加困惑,但无论哪种方式,我都很高兴我问了这个问题......感谢您花时间回答,每个人!


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

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

    在实践中,没有一种编程语言是无上下文的,因为诸如“一个变量必须在使用之前被声明”的东西不能通过上下文无关语法来检查(它们可以通过其他类型的语法来检查)。 这并不坏,实际上,要检查的规则分为两部分:您要检查语法的部分和检查语义部分的部分(并且此部分还允许更好的错误报告和恢复,所以有时候您有时为了让用户更好地诊断,我们希望在语法上接受更多的内容)。

    人们认为C ++不是上下文无关的意思是,做这种划分是不可能的(方便地包括作为标准“几乎遵循官方语言描述”和“我的语法分析器生成器工具支持那种划分“;允许语法模棱两可,并且通过语义检查来解决歧义是一种相对简单的方法来完成C ++的切割,并且完全遵循C ++标准,但是当您依赖工具时不方便不允许含糊不清的语法,当你有这样的工具时,这很方便)。

    我对D的了解不够,无法通过语义检查了解上下文无关的语法规则是否存在方便的切入点,但是您所展示的内容远没有证明没有这种情况。


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

    基本上,它意味着语言元素的定义不能根据哪个元素包围它。 通过语言元素,我的意思是表达式和​​标识符等概念,而不是程序内部这些概念的具体实例,如a + bcount

    让我们试着建立一个具体的例子。 考虑这个简单的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 。 我可以用我的语法重写第二部作品,如下所示:

    '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时调用成员函数模板。


    已经有很多很好的答案,但是由于你对语法,解析器和编译器等知之甚少,所以让我通过一个例子来演示这一点。

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

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

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

    *S* -> a *T* -> a *b G* t -> a a *Y* b t -> a a b t
    

    所以,我可以用这个语法创建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
    

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

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

    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语句,而不用考虑你所处的代码部分。 无论是开始还是中间,变量是否被定义,或者类型是否匹配,你都不知道,你也不在乎。

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

    executable -> variable equal expression
    

    你必须有这样的东西:

    declaration some_code executable -> declaration some_code variable equal expression
    

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

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

  • 类型检查
  • 函数参数的数量
  • 默认值为功能
  • 如果memberobj中存在objobj.member
  • 几乎所有不相似的东西:失踪;}
  • 我希望你知道有什么区别(如果你没有,我会很乐意解释)。

    总之:

  • Lexer使用正则语法来标记输入
  • 解析器使用上下文无关语法来确保程序结构正确
  • 语义分析器使用上下文敏感的语法来进行类型检查,参数匹配等
  • 尽管这并不总是这样。 这只是告诉你如何让每个关卡都变得更强大,以便能够做更多的事情。 然而,每个提到的编译器级别实际上可能更强大。

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

    如果您设计的语言使用的词法分析器具有重叠的正则表达式,那么您还需要查看上下文以确定您匹配的是哪种类型的标记。

    为了解决您的问题! 在你提到的例子中,显然c ++语法不是上下文无关的。 语言D,我绝对不知道,但你现在应该能够推断它。 这样思考:在上下文无关语法中,非终结符可以在不考虑任何事物的情况下扩展,但可以扩展语言的结构。 与您所说的相似,它会扩大,而不会在其他地方“看”。

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

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

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

    I go there because I want to
    

    要么

    I jump you that I is air
    

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

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

    链接地址: http://www.djcxy.com/p/74853.html

    上一篇: Is D's grammar really context

    下一篇: how to get linux command output string and output status in c++