如何在词法分析器生成器中有效地实现最长匹配?

Posted

技术标签:

【中文标题】如何在词法分析器生成器中有效地实现最长匹配?【英文标题】:How to efficiently implement longest match in a lexer generator? 【发布时间】:2014-07-21 12:55:42 【问题描述】:

我有兴趣学习如何编写像 flex 这样的词法分析器生成器。我一直在阅读“编译器:原理、技术和工具”(“龙书”),并且对 flex 的工作原理有了基本的了解。

我最初的方法是:用户将提供正则表达式的哈希映射,将正则表达式映射到令牌枚举。该程序将按照给定的顺序一个接一个地循环遍历正则表达式,并查看它们是否与字符串的开头匹配(我可以在每个正则表达式的开头添加一个^ 来实现这一点)。如果他们这样做了,我可以将该正则表达式的标记添加到程序的标记列表中。

我的第一个问题是,这是最有效的方法吗?目前我必须遍历每个正则表达式,但理论上我可以从所有组合的正则表达式构建一个 DFA,并更有效地逐步完成。但是,创建此 DFA 会产生一些开销。

我的第二个问题是,我将如何像 flex 那样实现最长匹配的字符串 tie break?即,我想匹配 ifa 作为标识符,而不是关键字 if 后跟字母 a。我没有看到任何有效的方法来使用正则表达式来做到这一点。我想我必须遍历所有正则表达式,尝试匹配它们,如果我有多个匹配项,则取最长的结果。但是,如果我将正则表达式转换为 DFA(即我自己的 DFA 数据结构),那么我可以继续遍历字符,直到 DFA 上不再有可能的过渡边缘。那时,我可以将我最后一次通过接受状态作为令牌的实际匹配,因为那应该是最长的匹配。

我的两个问题都指向我自己编写从正则表达式到 DFA 的翻译器。这是必需的,还是我仍然可以使用普通的正则表达式(由标准库实现)有效地做到这一点并且仍然获得最长的匹配?

编辑:我保留了我正在使用的正则表达式引擎,因为我想要一个通用的答案,但我使用的是 Rust 的正则表达式库:http://static.rust-lang.org/doc/master/regex/index.html

【问题讨论】:

您使用的是哪个正则表达式引擎? POSIX 引擎遵循最左最长规则,类似 Perl 的引擎通常使用最左优先匹配(因此您可以通过在正则表达式中将较长的子匹配放在首位来确保它是最长的)。 【参考方案1】:

在时间上,将所有正则表达式编译成一个并行匹配所有模式的自动机效率更高。不过,它可能会显着增加空间使用量(相对于模式大小,DFA 的状态可能呈指数级增长),因此值得研究这是否会造成伤害。

通常,您实现 maximal-munch(匹配最长的字符串)的方式是正常运行匹配的自动机。跟踪您找到的最后一个匹配项的索引。当自动机进入死态并停止时,您可以输出从标记开头向上通过匹配点的子字符串,然后在输入序列中跳回到匹配完成后的点。这可以非常有效地完成,而且完全不会减慢速度。

如果有帮助,here are some lecture slides from a compilers course I taught 探索扫描技术。

希望这会有所帮助!

【讨论】:

但是如果你使用正则表达式“1|(1*2)”来匹配字符串“1111111.....1”,DFA会运行到输入的末尾而不进入死状态。所以它会读到输入的末尾并每次都跳回来。运行时间为 O(n^2)。使用 DFA 可以在 O(n) 时间内完成字符串匹配吗? 你说得对,上次我教编译器课程时,我实际上把它作为练习!我不知道如何使这种最坏情况有效,但实际上这是一种非常快速的方法并且很少退化。 我在尝试检索此答案中链接的讲座幻灯片时收到“禁止访问”。

以上是关于如何在词法分析器生成器中有效地实现最长匹配?的主要内容,如果未能解决你的问题,请参考以下文章

Swift词法结构参考!

实现词法分析器时的 DFA 与正则表达式?

自制Lex-词法分析器生成器(C++)

python实现算术表达式的词法语法语义分析(编译原理应用)

C词法分析器的Python简单实现

词法分析实验报告