递归下降解析器应该更快
...或者你做错了什么。
首先,您的代码应分为 2 个不同的步骤。词法分析器+解析器。
一些在线参考示例会首先将整个语法标记化为一个大型中间数据结构,然后将其传递给解析器。虽然有利于演示;不要这样做,它会使时间和内存复杂性加倍。相反,一旦词法分析器确定匹配,就通知解析器状态转换或状态转换+数据。
至于词法分析器。这可能是您发现当前瓶颈的地方。如果词法分析器与解析器完全分离,您可以尝试在 Regex 和非 Regex 实现之间切换以比较性能。
无论如何,正则表达式并不比读取原始字符串快。默认情况下,它只是避免了一些常见错误。具体来说,不必要地创建字符串对象。理想情况下,你的词法分析器应该扫描你的代码并产生一个中间数据为零的输出,除了在你的解析器中跟踪状态所需的最低限度。记忆方面你应该有:
- 原始输入(即源)
- 解析器状态(ex isExpression、isSatement、row、col)
- 数据(例如 AST、树、二维数组等)。
例如,如果您当前的词法分析器匹配一个非终端并一个接一个地复制每个字符,直到它到达下一个终端;您实际上是在为每个匹配的字母重新创建该字符串。请记住,字符串数据类型是不可变的,concat 将始终创建一个新字符串。您应该使用指针算法或等效方法扫描文本。
要解决此问题,您需要从非终端的 startPos 扫描到非终端的末尾,并仅在匹配完成时复制。
Regex 默认开箱即用地支持所有这些,这就是为什么它是编写词法分析器的首选工具。与其尝试编写一个解析整个语法的正则表达式,不如编写一个只关注匹配终端和非终端作为捕获组的正则表达式。跳过标记化,并将结果直接传递到您的解析器/状态机。
这里的关键是,不要尝试将 Regex 用作状态机。充其量它只适用于Regular(即Chomsky Type III,无堆栈)声明性语法——因此得名Regular Expression。例如,HTML 是一种无上下文(即 Chomsky Type II,基于堆栈)的声明性语法,这就是为什么仅靠 Rexeg 永远不足以解析它的原因。您的语法以及通常所有的模板语法都属于这一类。您显然已经达到了 Regex 的极限,因此您走在正确的轨道上。
仅使用 Regex 进行标记化。如果您真的关心性能,请重写您的词法分析器以消除任何和所有不必要的字符串复制和/或中间数据。看看你是否能超越 Regex 版本。
关键是。Regex 版本更易于理解和维护,而如果编写正确,您的手动词法分析器可能会更快一点。传统智慧说,帮自己一个忙,更喜欢前者。就 Big-O 复杂性而言,两者之间应该没有任何区别。它们是同一事物的两种形式。