如何为同一文本调用多个词法分析器?

How to invoke multiple lexical analyzers for the same text?

假设我们有两个代码片段:

long l = 0L;
string sqlStr = "SELECT Column1, Column2 FROM Table ORDER BY Column3 DESC";
int i = 0;
// ... C# code goes on

/// <summary>
/// This method does something.
/// </summary>
/// <param name="a">The a parameter.</param>
/// <param name="b">The b parameter.</param>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="b"/> is <c>null</c>.
/// </exception>
public static void DoSomething(int a, string b)
{
    // ... etc
}

假设我写过 C#、SQL 和 XML 扫描器(我没有,但这不是这个问题的重点)。

当这个假设的 C# 扫描器遇到 sqlStr 带有内联 SQL 的字符串内容时,它必须 invoke/switch 到 SQL 扫描器(听起来很奇怪,但请耐心等待;它必须),将内容分析为 SQL 语言,然后切换回来继续扫描 C#。

同样,当在DoSomething方法上发现XML documentation comment时,C#扫描器必须切换到XML扫描器并生成XML令牌,然后完成, 继续作为 C#。

(显然,消费应用程序将区分 C#、SQL 和 XML 标记)

我的问题:

(f)lex 接口没有将缓冲区管理与词法分析分开,因此没有在扫描过程中“调用另一个词法分析器”的机制。 [注1]

但是,您可以使用 start conditions 轻松地装备生成的扫描器来处理不同的词汇上下文。每个条件都定义了一组完全独立的规则,因此只要可以使扫描器使用相同的语义值类型(在 C 中,这将涉及扩展语义联合),您就可以将任意数量的不同标记集打包到一个生成的语法。 [注2]

棘手的部分是让过渡正确,即使界面更加协作,这也会有点棘手。 LALR(1) 文法,由常见的解析器生成器生成,依赖于提前读取先行标记。执行解析器操作时,下一个输入标记通常已经处理完毕。因此,如果解析器操作调用扫描器过程来更改状态,则状态更改通常不会发生,直到标记 超前标记之后。

但即便如此也不能保证,因为解析器可能会选择执行缩减操作而不要求先行标记,在这种情况下,不管后面是什么标记都会执行缩减。 Bison 实现了这种优化,但并不是所有的解析器生成器都这样做,而且并不是所有的解析器生成器都记录了他们是否这样做。

因此,如果可能的话,让分隔符字符串在两种语言中使用相同的标记是很有用的。如果这不可能,也可以将语法编写为两个标记具有相同的效果。根据具体情况,还有各种其他 parser/scanner-specific 黑客可用。

除非在极少数情况下,否则不可能在扫描器操作中触发转换,因为这需要将解析的重要部分复制到扫描器中。但在极少数情况下,当分隔符字符串特别容易识别时,可能是可以的。

所有这些策略的“实际”示例都是可用的,包括在 flex 本身的代码中(它需要在一个上下文中识别正则表达式并在另一个上下文中识别嵌入式 C 代码)。


备注:

  1. 很难批评很久以前做出的设计决定。这是完全可以理解的。但是恕我直言,我们仍然要忍受它,这真的很不幸。控制流反向扫描器可用于各种应用程序,例如从事件循环内的输入流进行在线解析。

  2. 使用 flex,您不必使开始条件完全独立。条件可以是“排他性的”(只有特别标记为该条件一部分的模式适用)或非排他性的(未标记的模式也适用)。您选择哪个选项部分取决于令牌化的相似程度。