我在使用flex
编译此正则表达式时遇到问题"on"[ \t\r]*[.\n]{0,300}"."[ \t\r]*[.\n]{0,300}"from" {counter++;}
我在flex规范文件的规则部分有100个规则。我试图编译它flex -Ce -Ca rule.flex
我等了10个小时仍然没有完成所以我杀了它。我开始发现问题并将问题缩小到这条规则。如果我从100条规则中删除此规则,则需要21秒才能将其编译为C代码。
如果我用其他字符替换句点,它会成功编译。 e.g。
"on"[ \t\r]*[.\n]{0,300}"A"[ \t\r]*[.\n]{0,300}"from" {counter++;}
立即编译。即使是空格字符后面/前面的句点也会快速编译
"on"[ \t\r]*[.\n]{0,300}" ."[ \t\r]*[.\n]{0,300}"from" {counter++;}
我可以从flex manual看到“。”匹配文字“。”
我的规则有什么问题?
答案 0 :(得分:3)
简单的答案是[.\n]
可能不会按照您的想法行事。在字符类中,大多数元字符都失去了它们的特殊含义,因此字符类只包含两个字符:文字.
和换行符。您应该使用(.|\n)
。
但这不会解决问题。
根本原因是使用固定的重复计数。如果匹配区域的末端不明确,则大的(或甚至不那么大的)重复计数可导致状态机的指数爆炸。
重复[.\n]
时,重复匹配具有明确的终止,除非正则表达式的其余部分可以以点或换行符开头。因此"."
会触发问题,但"A"
却没有。如果您更正重复以匹配任何字符,则任何后续字符都将触发指数性爆炸。因此,如果您进行上述建议的更改,则正则表达式将继续无法编译。
将重复次数更改为无限期重复(星号运算符)可以避免此问题。
为了说明问题,我使用-v
选项检查具有不同重复次数的状态数。这清楚地显示了状态计数的指数增长,并且显然不可能超过14次重复。 (我没有显示时间消耗;足以说flex
的算法在DFA的大小上不是线性的,所以虽然每次额外的重复都会使状态数增加一倍,但它大约是时间消耗的四倍在16个州,flex需要45秒,因此可以合理地假设需要大约一周才能完成23次重复,前提是它需要的6GB内存可以在没有太多交换的情况下使用。我没有尝试实验。)
$ cat badre.l
%%
"on"[ \t\r]*[.\n]{0,XXX}"."[ \t\r]*[.\n]{0,XXX}"from"
$ for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14; do
> printf '{0,%d}:\t%24s\n' $i \
> "$(flex -v -o /dev/null <( sed "s/XXX/$i/g" badre.l) |&
> grep -o '.*DFA states')"
> done
{0,1}: 17/1000 DFA states
{0,2}: 25/1000 DFA states
{0,3}: 41/1000 DFA states
{0,4}: 73/1000 DFA states
{0,5}: 137/1000 DFA states
{0,6}: 265/1000 DFA states
{0,7}: 521/1000 DFA states
{0,8}: 1033/2000 DFA states
{0,9}: 2057/3000 DFA states
{0,10}: 4105/6000 DFA states
{0,11}: 8201/11000 DFA states
{0,12}: 16393/21000 DFA states
{0,13}: 32777/41000 DFA states
{0,14}: 65545/82000 DFA states
为两次重复更改正则表达式使用(.|\n)
大致使状态数增加三倍,因为随着更改两个重复变得模糊(并且两者之间存在交互)