【问题标题】:Flex rule with a period "." is not compiling带有句点“.”的弹性规则没有编译
【发布时间】:2016-03-08 08:02:50
【问题描述】:

我在用 flex 编译这个正则表达式时遇到问题

"on"[ \t\r]*[.\n]{0,300}"."[ \t\r]*[.\n]{0,300}"from"    {counter++;}

我在 flex 规范文件的规则部分有 10000 条规则。我试图编译它flex -Ce -Ca rule.flex 我等了 10 个小时仍然没有完成所以我杀了它。我开始发现问题并将问题缩小到这条规则。如果我从 100 条规则中删除这条规则,编译成 C 代码需要 21 秒。

如果我用其他字符替换句点,它将成功编译。例如

"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 看到那个“。”匹配文字“.”

我的规则有什么问题?

【问题讨论】:

  • 不要使用词法分析器进行解析。使用它们将标记彼此分开,并为自己编写一个单独的解析器,可以手动或使用解析器生成器(如 yacc/bison)。你永远不会从这里到达那里。
  • @EJP 我没有使用 flex 进行解析。我有数千个正则表达式,每个都代表一个文本消息模板。我正在使用 Java 的正则表达式来匹配模板。但这慢得让人无法接受。所以我使用 flex 将所有正则表达式编译成一个 DFA。它超快。我只想知道给定的短信是否与任何正则表达式匹配。
  • 正在使用flex进行解析。期间。
  • @EJP 无论如何,我认为这是一个合法的规则。如果您不这么认为,我很乐意纠正自己:)

标签: regex flex-lexer lex


【解决方案1】:

简单的答案是[.\n] 可能不会像您认为的那样做。在字符类中,大多数元字符都失去了它们的特殊含义,因此该字符类只包含两个字符:文字 . 和换行符。你应该使用(.|\n)

但这并不能解决问题。

根本原因是使用了固定的重复计数。如果匹配区域的末端不明确,那么大(或什至不是那么大)重复计数可能导致状态机的指数爆炸。

随着[.\n] 的重复,重复的匹配具有明确的终止,除非正则表达式的其余部分可以以点或换行符开头。所以"." 会触发问题,但"A" 不会。如果您纠正重复以匹配任何字符,那么任何后续字符都会触发指数爆炸。因此,如果您进行上述建议的更改,正则表达式将继续无法编译。

将重复计数更改为无限重复(星号运算符)将避免该问题。


为了说明问题,我使用了-v 选项来检查具有不同重复计数的状态数。这清楚地显示了状态计数的指数增长,很明显,超过 14 次重复是不可能的。 (我没有显示时间消耗;我只想说flex 的算法在 DFA 的大小上不是线性的,所以虽然每次额外的重复都会使状态数量增加一倍,但它大约会使时间消耗增加四倍;在16 个州,flex 用时 45 秒,所以假设它需要大约一周的时间来做 23 次重复是合理的,前提是它需要的 6GB RAM 可用而无需太多交换。我没有尝试这个实验。)

$ 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) 进行两次重复大约会使状态数量增加三倍,因为随着这种更改两个重复变得模棱两可(并且两者之间存在交互)。

【讨论】:

  • 我明白你说的,但它正在编译 [.\n] 问题是“。”当我用其他字符替换这个字符或者我在句点符号之前放一个空格,即“.”时,它会编译。我的问题是为什么会这样?
  • @aryaveer:我在下一段中解释。如果重复的模式可以匹配后面的第一个字符,你会得到具有固定重复计数的指数爆炸。所以"." 是个问题,因为[.\n] 匹配它。它与A 或空格不匹配,因此在这些情况下没有问题。
  • @aryaveer:现在有一个指数状态爆炸的插图,显示了各种(小)重复计数所需的状态数。
  • 你是对的。有没有办法匹配这种类型的模式 "on"[ \t\r]*(.){0,300}"A"[ \t\r]*(.){0,300}"from" {counter++;}我们可以使用解析器或其他工具吗?
  • @aryaveer,如果你能描述你想要解析的内容,任何东西都是可解析的。但是“这种类型的模式......”并没有告诉我任何事情。你到底想匹配什么?我严重怀疑您正在寻找的是“由单词'on'组成的最长字符串,后跟最多600个几乎任意字符(可能包含单词'from'),然后是单词'from',其中有一个'A' 从分隔字符的任一端开始不超过 300 个字符。”如果那 你想要的,那么你可以通过先切割字符串来避免固定的重复......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-11-11
  • 2013-09-20
  • 1970-01-01
  • 2018-01-28
  • 2015-04-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多