Flex中的非贪婪正则表达式匹配

时间:2013-10-21 07:45:07

标签: regex flex-lexer lex

我刚开始使用Flex,似乎无法弄清楚如何匹配以下表达式:

"Dog".*"Cat"
------------------
Input :
Dog Ca Cat Cc Cat
------------------
Output:
Dog Ca Cat Cc Cat

但我想要一个非贪婪的匹配,输出如下:

Output:
Dog Ca Cat

如何在Flex上实现这一目标?

修改

尝试以下方法:

%%
Dog.*Cat/.*Cat  printf("Matched : ||%s||", yytext);
dog.*cat        printf("Matched : ||%s||", yytext);
dOg[^c]*cAt     printf("Matched : ||%s||", yytext);
DOG.*?CAT       printf("Matched : ||%s||", yytext);
%%

输入:

Dog Ca Cat Cc Cat
dog Ca cat Cc cat
dOg Ca cAt Cc cAt
DOG CA CAT CC CAT

输出

Matched : ||Dog Ca Cat Cc Cat||
Matched : ||dog Ca cat Cc cat||
Matched : ||dOg Ca cAt|| Cc cAt
Matched : ||DOG CA CAT CC CAT||

还收到警告:

lex4.l:2: warning, dangerous trailing context

Flex版本:

flex 2.5.35 Apple(flex-31)

3 个答案:

答案 0 :(得分:5)

这是使用lex / flex工具的常见问题,这些工具会让初学者(有时候是非初学者)难倒。该问题有两种解决方案,需要两种不同的工具高级功能。像dog ... cat这样的短语与各种编程语言中的匹配评论大致相同,例如 C 评论表单/* ... */甚至'comment' ... 'tnemmoc'。它们与您的示例具有完全相同的特征。请考虑以下 C 代码:

/* This is a comment */ "This is a String */"

这种贪婪的词汇匹配会与错误的评论终结符相匹配(对学生词法分子BTW来说是一个很好的考验!)。

有几个大学编译课程的建议解决方案。解释得很好的是here (at Manchester)。其中引用了几本也涵盖问题的好书:

  • J.Levine,T.Mason& D.Brown:Lex和Yacc(第2版)
  • M.E.Lesk& E.Schmidt:Lex - 词汇分析器生成器

所描述的两种技术是使用Start Conditions明确指定状态机,或manual input直接读取字符。

对于您的cat ... dog问题,可以通过以下方式对其进行编程:

开始条件

在这个解决方案中,我们需要几种状态。关键字dog会导致其进入DOG状态,直到遇到字母c为止。然后进入LETTERC状态,必须后跟字母a,否则DOG状态继续;字母a会导致输入CAT状态,后面必须跟一个字母t,这会导致整个短语匹配并返回INITIAL状态。 yymore导致整个dog ... cat文本被保留以供使用。

%x DOG LETTERC CAT
d [dD]
o [oO]
g [gG]
c [cC]
a [aA]
t [tT]
ws [ \t\r\n]+
%%

<INITIAL>{d}{o}{g} {
        BEGIN(DOG);
        printf("DOG\n");
        yymore();
        }
<DOG>[^cC]*{c} {
        printf("C: %s\n",yytext);
        yymore();
        BEGIN(LETTERC);
        }
<LETTERC>{a} {
       printf("A: %s\n",yytext);
       yymore();
       BEGIN(CAT);
      }
<LETTERC>[^aA] {
        BEGIN(DOG);
        yymore();
        }
<CAT>{t} {
        printf("CAT: %s\n",yytext);
        BEGIN(INITIAL);
        }
<CAT>[^tT] {
        BEGIN(DOG);
        yymore();
        }
<INITIAL>{ws}  /* skip */ ;

手动输入

手动输入法只匹配起始短语dog和输入 C 代码,该代码吞下输入字符,直到遇到所需的cat序列。 (我没有打扰大写和小写字母)。此解决方案的问题在于难以将输入文本值保留在yytext中以供稍后在解析器中使用。它会丢弃它,如果构造是注释,那就没关系,但是没有其他用处。

d [dD]
o [oO]
g [gG]
ws [ \t\r\n]+
%%
{d}{o}{g}   {
   register int c;

                     for ( ; ; )
                         {
                         /* Not dealt with upper case .. left as an exercise */
                         while ( (c = input()) != 'c' &&
                                 c != EOF )
                             ;    /* eat up text of dog */

                         if ( c == 'c' )
                             {
                              if ( ( c = input()) == 'a' )
                                     if ( (c = input()) == 't' )
                                 break;    /* found the end */
                             }
                        if ( c == EOF )
                             {
                             REJECT;
                             break;
                             }
                         }
            /* because we have used input() yytext always contains "dog" */
            printf("cat: %s\n", yytext);
       }
{ws}  /* skip */ ;

(这两种解决方案都经过测试)

答案 1 :(得分:0)

一个好问题。这是一个纯正则表达式解决方案,不使用非贪婪的.*?语法:

Dog([^C]|C+(aC+)*([^Ca]|a[^Ct]))*C+(aC+)*at

答案 2 :(得分:0)

这是针对此问题的最小 C++ flex 词法分析器。非贪婪匹配的关键是 flex 手册和其他地方提到的开始条件。

开始条件只是词法分析器的另一种状态。当需要非贪婪匹配时,有些模式需要在第一次匹配时终止匹配

通常,无论状态如何,如果您正在寻找目标字符串或模式,您只需要确保没有其他更通用的模式可以匹配包含目标模式的更长输入段

当目标模式是有条件的并且需要在一些较早的匹配后启用时,启动条件会有所帮助。您打开开始条件以启用匹配目标模式,然后通过将状态重置为 0 或 INITIAL 将其关闭 - 或者切换到另一个状态以进行更多条件匹配

状态通过 BEGIN 切换 - 还有一个状态堆栈可通过 yy_push_stateyy_pop_state 使用

flex 手册中有很多启动条件的例子

以下是显示非贪婪匹配与 flex 开始条件的 flex 规则 - 词法分析器匹配一行上第一次出现的狗,直到第一次出现 cat - 匹配不区分大小写

完整的文件放在最后——对于不熟悉 flex 的人,请注意许多行以空格开头——这不是偶然的,也是 flex 所要求的

%%
 /* flex rules section */

 string match;

dog {
// found a dog, change state to HAVE_DOG to start looking for a cat
 BEGIN(HAVE_DOG);
// save the found dog
 match = yytext;
}
 /* save and keep going till cat is found */
<HAVE_DOG>. match += yytext;

<HAVE_DOG>cat {
// save the found cat
 match += yytext;
// output the matched dog and cat
 cout << match << "\n";
// ignore rest of line
 BEGIN(SKIP_LINE);
}

 /* no cat on this line, reset state */
<HAVE_DOG>\n BEGIN(0);
 /* rules to ignore rest of the line then reset state */
<SKIP_LINE>{
  .*
  \n BEGIN(0);
}

 /* nothing to do yet */
.|\n

这是一些测试输入

$ cat dogcat.in.txt

Dog Ca Cat Cc Cat
dog Ca cat Cc cat
dOg Ca cAt Cc cAt
DOG CA CAT CC CAT
cat dog dog cat cat
dog kitten cat dog cat
dig cat dog can dog cut
dig dug dog cow cat cat
doc dogcat catdog
dog dog dog
cat cat cat

构建

flex -o dogcat.flex.cpp dogcat.flex.l && g++ -o dogcat dogcat.flex.cpp

一起跑

$ ./dogcat < dogcat.in.txt

Dog Ca Cat
dog Ca cat
dOg Ca cAt
DOG CA CAT
dog dog cat
dog kitten cat
dog cow cat
dogcat

完整的 flex 文件

 /* dogcat.flex.l */

 /*
 Build with:
 flex -o dogcat.flex.cpp dogcat.flex.l && g++ -o dogcat dogcat.flex.cpp
 */

 /*
 A minimal C++ flex lexer that shows nongreedy matching with flex
 start conditions
 matches the first occurrence of dog on a line till the first
 occurrence of cat 
 matching is case insensitive
 */
 /* C++ lexer using yyFlexLexer in FlexLexer.h */
%option c++

 /* case-insensitive patterns */
%option case-insensitive

 /* generate main function for executable */
%option main

 /* all input must be matched, no echo by default */
%option nodefault

 /* debug output with lexer.set_debug(1) */
%option debug
 /* start condition means dog was matched */
%x HAVE_DOG
 /* start condition means to ignore remaining line */
%x SKIP_LINE
%{

#include <string>
#include <iostream>

// C++ flex lexer class
// needed because header itself has no guard
#ifndef yyFlexLexerOnce
#  include <FlexLexer.h>
#endif

using namespace std;

namespace {
// the C++ lexer class from flex
  yyFlexLexer lexer;

// main generated by flex still calls free yylex function even for C++ lexer
  int yylex() {
    return lexer.yylex();
  }
}

%}
%%
 /* flex rules section */

 string match;

dog {
// found a dog, change state to HAVE_DOG to start looking for a cat
 BEGIN(HAVE_DOG);
// save the found dog
 match = yytext;
}
 /* save and keep going till cat is found */
<HAVE_DOG>. match += yytext;

<HAVE_DOG>cat {
// save the found cat
 match += yytext;
// output the matched dog and cat
 cout << match << "\n";
// ignore rest of line
 BEGIN(SKIP_LINE);
}

 /* no cat on this line, reset state */
<HAVE_DOG>\n BEGIN(0);
 /* rules to ignore rest of the line then reset state */
<SKIP_LINE>{
  .*
  \n BEGIN(0);
}

 /* nothing to do yet */
.|\n