4

在经典的编译器理论中,前两个阶段是词法分析和解析。他们正在筹备中。词法分析将标记识别为 Parsing 的输入。

但是我遇到了一些在词法分析中很难正确识别的案例。例如以下关于 C++ 模板的代码:

map<int, vector<int>>

>>“常规”词法分析中,这将被识别为按位右移,但这是不正确的。我的感觉是很难将这种语法的处理分为两个阶段,词法工作必须在解析阶段完成,因为正确解析>>依赖于语法,而不仅仅是简单的词法规则。

我想知道关于这个问题的理论和实践。另外,我想知道 C++ 编译器如何处理这种情况?

4

2 回答 2

6

C++ 标准要求实现在解析阶段之前执行词法分析以生成标记流。根据词法分析规则,两个连续>的字符(不跟在后面=)总是会被解释为一个>>记号。C++ 标准提供的语法是根据这些标记定义的。

在某些上下文中(例如>在模板 ID 中期望 a 时)实现应解释>>为 2>的要求并未在语法中指定。相反,该规则被指定为一种特殊情况:

14.2 模板特化的名称 [temp.names] ###

在名称查找 (3.4) 发现名称是模板名称operator-function-idliteral-operator-id引用一组重载函数后,其中任何成员都是函数模板,如果后面跟着a <<始终将其作为模板参数列表的分隔符,而从不作为小于运算符。解析模板参数列表时,将第一个非嵌套>的作为结束分隔符而不是大于运算符。类似地,第一个非嵌套>>的被视为两个连续但不同的>标记,其中第一个被视为模板参数列表的结尾并完成 模板 ID。[注意:此替换规则产生的第二个>标记可能会终止封闭的 模板ID构造,或者它可能是不同构造的一部分(例如演员表)。-结束注释]

请注意前面的规则,在某些情况下,<应将其解释为<template -argument-list 中的。这是另一个需要上下文以消除解析歧义的构造示例。

C++ 语法包含许多这样的歧义,在解析过程中如果没有上下文信息就无法解决这些歧义。其中最著名的被称为Most Vexing Parse,其中标识符可以根据上下文解释为类型名称。

在 C++ 中跟踪上述上下文需要实现与解析阶段并行执行一些语义分析。这通常以在给定上下文中识别特定语法结构时调用的语义动作的形式实现。然后,这些语义动作构建了一个表示上下文并允许有效查询的数据结构。这通常被称为符号表,但 C++ 所需的结构几乎是整个AST

这些上下文敏感的语义动作也可以用来解决歧义。例如,在识别namespace-body上下文中的标识符时,语义操作将检查该名称是否先前被定义为模板。然后将其结果反馈给解析器。这可以通过用结果标记标识符标记来完成,或者用匹配不同语法规则的特殊标记替换它。

可以使用相同的技术将 a 标记为模板参数列表<的开头,或将 a标记为结尾。用两个进行上下文敏感替换的规则本质上是相同的问题,并且可以使用相同的方法解决。>>>>

于 2013-10-01T10:41:00.037 回答
1

你是对的,词法分析器和解析器之间的理论上的清晰区别并不总是可能的。我记得我在学生时做过的一个项目。我们要实现一个 C 编译器,我们用作基础的语法将在某些情况下将类型定义的名称视为类型,在其他情况下视为标识符。所以词法分析器必须在这两种模式之间切换。我当时实现这个的方式是使用特殊的空规则,它根据上下文重新配置词法分析器。要做到这一点,重要的是要知道解析器总是会使用一个前瞻标记。因此,对词法分析器行为的任何更改都必须在受影响的位置之前至少发生一个词法标记。最后,这工作得很好。

>>您提到的 C++ 案例中,我不知道编译器实际上做了什么。willj 引用了规范如何表达这一点,但允许实现在内部做不同的事情,只要可见的结果是相同的。所以这就是我尝试解决这个问题的方法:在读取 a>时,词法分析器会发出 token GREATER,但也会切换到一个状态,其中每个后续> 没有空格的都将被 lexed 到GREATER_REPEATED. 任何其他符号都会将状态切换回正常状态。除了状态切换,您还可以通过对正则表达式进行词法分析>+并从该规则发出多个标记来做到这一点。在解析器中,您可以使用如下规则:

rightAngleBracket: GREATER | GREATER_REPEATED;
rightShift: GREATER GREATER_REPEATED;

运气好的话,您可以使模板参数规则使用 rightAngleBracket,而表达式使用 rightShift。根据你的解析器有多少前瞻,可能需要引入额外的非终结符来保存更长的模糊内容序列,直到你遇到一些允许你最终在这些情况之间做出决定的上下文。

于 2013-10-01T11:07:11.800 回答