在C ++编译过程中,上下文敏感性在哪里得到解决?

时间:2009-07-24 20:02:32

标签: c++ parsing compiler-construction

昨天我询问了C ++上下文敏感度,请参阅here。在许多优秀的答案中,heredmckee已被接受的答案。

然而,我仍然认为有一些事情要说(可能是一些术语上的混淆?)。问题相当于:汇编的哪一部分涉及歧义?

澄清我的术语:CFG是一种语法,在规则的左侧只有一个非终端符号(例如A->zC),CSG是一个有终端的语法(在左侧(aAv->QT)加上一个非终端),其中大写字母是非终结符,小写是终结符。

在解析C ++源代码的语法中是否有像后者那样的表示?

谢谢你,很抱歉推动这个问题。

7 个答案:

答案 0 :(得分:3)

我知道没有C ++前端(解析器,名称/类型解析器) (including the one we built)在您定义时使用CSG语法规则实现上下文敏感的解析器。他们几乎明确地或隐含地使用仍然存在歧义的无上下文语法。

他们中的许多人使用自上而下的解析和交织的类型信息集合来区分模糊的情况。

将解析和类型集合编织在一起使得构建解析器变得非常麻烦和困难,产生民间定理“C ++难以解析”。

像许多这样的“定理”一样,它不是真的,除非你还坚持用一只手绑在背后解析(例如,递归下降,LL(k),LALR [例如,YACC])。如果您使用可以处理歧义的解析技术,那么C ++语法实际上并不那么难。我们(和其他人,如 Elsa C ++解析器)出于这个原因使用GLR解析技术。 (我们更进一步,在C ++语法中捕获宏定义,使用和预处理器条件,因为我们感兴趣的是在处理器破坏之前转换代码;通常预处理器指令在独立的预处理器中完全独立处理。)

Elsa我认为仍然将歧义解决方案交错到解析过程中,但由于解析非常简洁,因此更容易实现。我们的前端使用模糊节点构建AST,并且在构建树之后,我们使用属性语法评估器来遍历树以收集名称和类型,并消除那些类型不一致的歧义分支。后者是一个非常漂亮的方案,完全解析解析和名称解析。

难的是实际进行名称解析。 C ++有一个非常神秘的方案来查找,并且它分布在600页的标准中,以及各种方言的各种方式。将名称解析与解析分开使得这种丑陋更易于管理,但民间定理应该是“C ++很难命名解决”。

答案 1 :(得分:1)

首先,语言和语法之间存在差异。一个 language是一组字符串。语法是描述一组语法的一种方式 字符串(人们经常说语法“生成”字符串)。给定的 语言可以用几种语法来描述。

最着名的语法是基于生产的语法。那些 由乔姆斯基分类的

  • 不受限制的语法,在这两个方面可以有任何东西 制作

  • 单调语法,左手边最多只有 右手边

  • 上下文相关,其中只展开了一个非终端

  • 无上下文,其中制作的左侧只有一个 非终端

  • 常规语法,其中制作的左侧仅包含 生产的一个非终端和右侧可能只有一个 非终端,作为最新元素。

单调和上下文敏感的语法也称为类型1语法。 他们能够生成相同的语言。他们的力量不如 输入0语法。 AFAIK,虽然我看过有证据证明有语言 它有一个0型语法,但没有1型,我知道没有例子。

上下文相关语法称为类型2语法。他们少了 强大于1型语法。语言的标准示例 没有类型2语法,但类型1语法是字符串集 由相同数量的a,b和c组成,a和b之前的a c之前的b。

常规语法也称为类型3语法。它们不那么强大 比2型语法。那里的语言的标准例子 不是类型3语法,而类型2语法是字符串集合 正确匹配括号。

语法中的歧义是该等级之外的东西。语法是 如果给定的字符串可以通过多种方式生成,则不明确。有 不明确的1型语法,并且有不明确的3型语法。

然后还有其他种类的语法,这些语法不属于乔姆斯基 分类(两级语法,属性语法,树邻接 语法,...)即使它们是基于制作。其中一些是 甚至能够描述编程语言的语义。

然后是解析算法。那些通常是基于CFG和强加 更多限制以获得更好的解析速度(解析CSG需求) 指数时间,CFG需要立方时间,常用算法只需线性 时间)。这些限制引入了其他类语法。

实际上,CSG和单调语法几乎没有用来描述或编译 编程语言:他们的全球行为并不明显 由当地财产合成,因此很难理解和 将语义附加到生产是有问题的,解析它们是昂贵的 - 一般指数 - 并且错误处理很困难。非乔姆斯基 引入语法来解决这些问题。

回到C ++。该标准描述了无上下文的C ++语言 语法,但

  • 存在歧义(着名的“最令人烦恼的解析”)。所以编译器 必须认识到含糊不清并使用正确的解释(即 C x();是函数声明,而不是对象定义。)

  • 语法不是LR(1)(CFG最知名的子集之一) 其中存在线性解析算法)。其他算法(可能 使用更昂贵的时间或空间),或者基于更一般的 理论或通过推文线性使其适应C ++规则。简化 语法和拒绝错误接受的程序 语义分析也是一种可能性。

  • 字符串和终端之间的对应关系是 修改(主要由类型和模板声明,需要采取 考虑到模板定义已经解决了使用 依赖名称的typenametemplate)。这是通过拥有来解决的 lexing阶段查询符号表,以便给定字符串 根据具体情况,终端或其他终端。

  • 还有其他限制(需要声明一些标识符, 类型检查,...)在一个更不正式的变体中描述 英语。即使有些更强大,这通常也被认为是语义的 语法描述可以处理它们。

答案 2 :(得分:1)

@Ira Baxter说:“我所知道的没有C ++前端(解析器,名称/类型解析器)(包括我们构建的那个)使用CSG语法规则实现了一个上下文敏感的解析器。”

有一个上下文敏感的语法,Meta-S发行版处理“语法”中的许多C ++解析问题,而不是解析的任何其他阶段。例如 - 前向成员数据和成员函数引用可以在内联成员函数中处理,仅在解析阶段处理。

传统的parses在Abstract-Syntax-Tree传递或编译的其他阶段处理许多CS问题。

所以我的答案是:在“大多数”系统中,在解析后处理ambigutity。在至少一个系统中,大部分系统都在解析中处理。 Ergo,“在解析C ++源代码的语法中是否有类似后者的表示?”答案是 -

是。至少在Meta-S中,类型0 CS解析在语法中是可行的。

答案 3 :(得分:0)

我敢说,语义分析应该解决任何上下文敏感问题。

答案 4 :(得分:0)

因为没有其他事情在加紧(并且承认我对“真正的”编译器所做的事情非常粗略):

围绕像

这样的陈述的含糊不清
B = A();

通过查阅符号表来解决,以找出语句中的任何非关键字,非运算符的类型(此处为BA),并尝试形成可接受的分配他们如果找不到这样的安排,请发出错误。

这可能是在解析过程早期AST形成期间完成的,但是在lexing完成之后。

答案 5 :(得分:0)

  

编译的哪个部分涉及   含糊不清?

构建语法树后应该没有歧义。所以AFAIK的最终语法树应该可以进行翻译了。简而言之,语法分析器应该在派生过程中解决上下文敏感语法(AKA 模糊语法)(AKA 语法分析)。

答案 6 :(得分:0)

您似乎假设编译器是由严格基于语法的阶段构建的。没有东西会离事实很远。语法用于解析输入的一些位,然后使用各种启发式来解析其他位,然后进行更多的语法解析等等。在教科书中精心描述的理想编译器并不存在。

一个简单的例子是递归下降编译器,它使用表驱动的解析器来表示算术表达式。或者实际上,反之亦然。