零或多或一个或多个修饰符和回溯

时间:2011-08-27 21:44:03

标签: regex parsing computer-science backtracking ebnf

我在我的PEG解析器中添加了零或多和一个或多个修饰符,这很简单,因为PEG中的回溯非常少。之前的迭代从未被重新考虑过,因此简单的while循环就足够了。

但是,在其他情况下,零或多和一个或多个修饰符确实需要回溯。例如,采用以下正则表达式:

(aa|aaa)+

这个表达式应该能够贪婪地匹配七个a的字符串:有几种方法可以将2和3加起来得到7.但要实现这一点,重新考虑早期的迭代是必要的。例如,如果表达式第一次匹配三个a,第二次匹配三个a,则只剩下一个a,这是无法匹配的。然后,回溯最后三个a并匹配两个a,然后匹配五个a。然后最后两个a也可以匹配(即3 + 2 + 2 = 7)。

幸运的是,正则表达式在匹配字符串后退出搜索。但是EBNF解析器呢?如果语法不明确,解析器将使用回溯来查找所有可能的语法树!如果我们有生产

( "aa" | "aaa" )*

和七个a的字符串,一个完全回溯的解析器将返回所有可能的表达方式为2和3的方式。这只是七个a的:匹配a稍微长一点的字符串, N - 可能树的另一个级别。考虑 N = 6:

S : ( T )*
  ;

T : A
  | B
  | C
  | D
  | E
  | F
  ;

可怕的组合爆炸!

这个真的可以吗? EBNF中的零或多和一个或多个修饰符没有限制吗?如上所述实现它们比PEG解析器的普通while()循环要多得多,所以我不得不怀疑......

1 个答案:

答案 0 :(得分:7)

是;回溯可以给你很多结果。我是lepl的作者,这是一个递归的正确解析器,它将很乐意回溯并生成所有可能的AST的“解析林”。并且EBNF没有限制(这只是一种规范语言,并不依赖于任何特定的解析器实现)。

但并非所有解析算法都会回溯。正则表达式的许多实现都这样做,但并不总是必要的。事实上,对于一个“简单”的正则表达式(一个真正局限于常规语法的表达式),它可以在没有回溯的情况下进行匹配 - 从某种意义上说,诀窍是并行运行。

有两种(等效的)方法 - 通过“编译”正则表达式(如果并行工作是显式的那样计算表达式),或者通过在运行时处理并行匹配。编译方法将正则表达式转换为DFA(确定性有限自动机)。更确切地说,NFA(非确定性的......)模糊地类似于正则表达式的图形版本,并且可能是您想象正则表达式的工作方式;与NFA匹配确实需要回溯,但您可以将NFA转换为不包含DFA的DFA。

然而,在运行时执行此操作更容易理解(并且在实践中往往更有用),并在三篇 awesome 文章中进行了解释,如果您想要更好地理解这些文章,您应该阅读这些文章: http://swtch.com/~rsc/regexp/regexp3.html以及开头的链接。

我不能强调这一点 - 你需要阅读那些文章......

ps模糊相关 - 您可以通过缓存稍后可能需要的结果(当您最终通过不同的路径到达相同的文本和表达式时)来使回溯更有效。这被称为“packrat解析”,当应用于递归正常解析时(虽然说实话它不值得一个单独的名称 - 它实际上只是使用缓存)。缓存避免了指数运行时间 - 诺维格(谷歌的那个人,但之前就是这样写过)的某个地方有一篇论文解释了:http://acl.ldc.upenn.edu/J/J91/J91-1004.pdf