首页 > 解决方案 > 如何为同一文本调用多个词法分析器?

问题描述

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

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 的字符串内容时,它必须调用/切换到 SQL 扫描器(听起来很奇怪,但请容忍我;它必须这样做),将内容分析为 SQL 语言,然后切换回来并继续扫描 C#。

同样,当在方法上找到XML 文档注释DoSomething时,C# 扫描器必须切换到 XML 扫描器并生成 XML 令牌,然后在完成之后,继续作为 C#。

(显然,消费应用程序会区分 C#、SQL 和 XML 令牌)

我的问题:

标签: flex-lexerlexjflex

解决方案


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

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

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

但即使这样也不能保证,因为解析器可能会选择执行归约操作而不要求前瞻标记,在这种情况下,无论后面是什么标记都将执行归约。Bison 实现了这种优化,但并不是所有的解析器生成器都这样做,甚至不是所有的解析器生成器都记录了他们是否这样做。

因此,如果可能的话,使定界符字符串在两种语言中都是相同的标记是很有用的。如果这是不可能的,也可以以两种标记具有相同效果的方式编写语法。根据情况,还有各种其他解析器/扫描器特定的黑客可用。

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

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


笔记:

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

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


推荐阅读