具有句点“。”的Flex规则。没有编译

时间:2016-03-08 08:02:51

标签: regex flex-lexer lex

我在使用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看到“。”匹配文字“。”

我的规则有什么问题?

1 个答案:

答案 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)大致使状态数增加三倍,因为随着更改两个重复变得模糊(并且两者之间存在交互)