flex学习 输入的匹配和动作

第五章输入是怎样被匹配的
当生成的扫描器运行时,它分析它的输入,寻找与它的任何模式匹配的字符串。如果它找到多个匹配项,则取匹配最多文本的那个(对于尾随上下文规则,这包括尾随部分的长度,尽管它随后会返回到输入中)。如果它找到两个或多个相同长度的匹配项,则选择flex输入文件中首先列出的规则。
一旦确定匹配,匹配对应的文本(称为标记)在全局字符指针yytext中可用,其长度在全局整数yyleng中可用。然后执行与匹配的模式相对应的操作(参见actions),然后扫描剩余的输入以寻找另一个匹配。
如果没有找到匹配项,则执行默认规则:将输入中的下一个字符视为匹配并复制到标准输出。因此,最简单的有效的flex输入是:
%%
它生成一个扫描器,只需将其输入(一次一个字符)复制到其输出。
请注意,yytext可以用两种不同的方式定义:作为字符指针或作为字符数组。你可以通过在flex输入的第一个(定义)部分中包含一个特殊指令%pointer或%array来控制flex使用哪个定义。默认是%pointer,除非你使用’-I’ lex兼容性选项,在这种情况下,yytext将是一个数组。使用%pointer的优点是扫描速度更快,并且在匹配非常大的标记时不会出现缓冲区溢出(除非动态内存用完)。缺点是操作修改yytext的方式受到限制(参见actions),并且调用unput()函数会破坏yytext的当前内容,在不同lex版本之间迁移时,这可能是一个相当令人头疼的移植问题。
%array的优点是你可以修改yytext你想要的内容,并且调用unput()不会破坏yytext(参见actions)。此外,现有的lex程序有时使用以下形式的声明从外部访问yytext:
extern char yytext[];
此定义在与%pointer一起使用时是错误的,但对于%array是正确的。
%array声明将yytext定义为一个包含YYLMAX个字符的数组,该数组的默认值相当大。您可以通过在flex输入的第一部分中简单的#define YYLMAX为不同的值来改变大小。如上所述,使用%pointer,yytext动态增长以适应较大的标记。虽然这意味着%pointer扫描器可以容纳非常大的标记(例如匹配整个注释块),但请记住,每次扫描器必须调整yytext的大小时,它还必须从头重新扫描整个标记,因此匹配此类标记可能会很慢。如果调用unput()导致太多文本被回退,yytext目前不会动态增长;相反,结果是运行时错误。
另外请注意,您不能在C++扫描程序类中使用%array(参见Cxx)。
第六章 动作
在规则中的每个模式都有相应的动作,可以是任意的C语句。模式在第一个非转义空格字符处结束;这个行的其余部分是它的动作。如果动作为空,那么当模式匹配时,输入标记将被丢弃。例如,下面是一个程序的规范,它从输入中删除所有出现的’zap me’:
%%
"zap me"
这个示例将把输入中的所有其它字符复制到输出中,因为它们将由默认规则匹配。
下面是一个程序,它将多个空格和制表符压缩为一个空格,并丢弃在行尾发现的空白:
%%
[ \t]+ putchar( ' ' );
[ \t]+$ /* ignore this token */
如果动作包含’{’,则该动作将一直持续到找到对应的’}’为止,并且该动作可能跨越多个行。flex了解C字符串和注释,不会被其中的大括号所欺骗,但也允许以’%{’开始的操作,并将认为该操作是下一个’%}’之前的所有文本(不管操作中是否由普通的大括号)。
仅由竖条(|)组成的动作意味着“与下一个规则的动作相同”。请看下面的插图。
动作可以包含任意的C代码,包括返回语句,将值返回给任何名为yylex()的程序。每次调用yylex()时,它都会从上次停止的地方继续处理标记,直到到达文件的末尾或执行返回。
动作可以自由的修改yytext,除了增加它的长度(在它的末尾添加字符,这个将覆盖输入流中后面的字符)。然而,这在使用%array时不适用(请参阅pattern)。在这种情况下,yytext可以以任何方式自由修改。
动作可以自由的修改yyleng,除非动作还包括使用yymore()(见下文)。
有一些特殊的指令可以包含在一个动作中:
ECHO
复制yytext到扫描器的输出
BEGIN
后面紧跟着启动条件的名称,将扫描器置于相应的启动条件中(见下文)。
REJECT
指示扫描器继续到匹配输入(或输入的前缀)的”次优”规则。如上所述在匹配中选择规则,并适当设置yytext和yyleng。它可能匹配与最初选择的规则一样多的文本,但后来出现的flex输入文本中,或者匹配较少的文本。例如,以下代码将对输入中的单词进行计数,并在看到’frob’时调用special()函数:
int word_count = 0;
%%

frob        special(); REJECT;
[^ \t\n]+   ++word_count;

如果没有REJECT,输入中出现的任何’frob’都不会被计算为单词,因为扫描器通常对每个标记只执行一个动作。允许多次使用REJECT,每次使用都会找到当前活动规则的下一个最佳选择。例如,当下面的扫描器扫描标记’abcd’时,它会在输出中写入’abcdabcaba’:
%%
a |
ab |
abc |
abcd ECHO; REJECT;
.|\n /* eat up any unmatched character /
前三个规则共享第四个规则的动作,因为它们使用了特殊的’|’动作。
REJECT在扫描器性能方面是一个特别昂贵的功能;如果在扫描器的任何动作中使用它,它将减慢扫描器的所有匹配。此外,REJECT不能与’-Cf’或’-CF’选项一起使用(参见扫描器选项)。
请注意,与其它特殊操纵不同,REJECT是一个分支。操作中紧随其后的代码将不会被执行。
yymore()
告诉扫描器下次匹配规则时,应该将相应的标记附加到yytext的当前值上,而不是替换它。例如,给定输入’mega-kludge’,下面的代码将在输出中写入’mega-mega-kludge’:
%%
mega- ECHO; yymore();
kludge ECHO;
第一次’mega-’匹配并回显到输出中。然后匹配’kludge’,但是前面的’mega-’仍然挂在yytext的开头,所以’kludge’规则的ECHO实际上会写’mega-kludge’。
关于yymore()使用的两个注意事项。首先,yymore()取决于yyleng的值是否正确反映当前标记的大小,因此如果使用yymore(),则不能修改yyleng。其次,yymore()在扫描器动作中的存在会对扫描器的匹配速度造成轻微的性能损失。
yyless(n)将当前标记的前n个字符以外的所有字符返回到输入流,当扫描器寻找下一个匹配时,它们将被重新扫描。yytext和yyleng被适当的调整(例如,yyleng现在将等于n)。例如,在输入’foobar’时,下面将写出’foobarbar’:
%%
foobar ECHO; yyless(3);
[a-z]+ ECHO;
从0到yyless()的参数将导致重新扫描整个当前输入字符串。除非您更改了扫描器随后处理其输入的方式(例如,使用BEGIN),否则这将导致无限循环。
请注意,yyless()是一个宏,只能在flex输入文件中使用,不能从其它源文件中使用。
unput(c)将字符c放回输入流。它将是下一个被扫描的字符。下面的动作将获取当前标记,并将其重新扫描到括号中。
{
int i;
/
Copy yytext because unput() trashes yytext */
char yycopy = strdup( yytext );
unput( ')' );
for ( i = yyleng - 1; i >= 0; --i )
unput( yycopy[i] );
unput( '(' );
free( yycopy );
}
请注意,由于每个unput()都将给定的字符放回到输入流的开头,因此必须前后回退字符串。
使用unput()时一个重要的潜在问题是,如果使用%pointer(默认值),则调用unput()会破坏yytext的内容,从最右边的字符开始,每次调用都会删除左边的一个字符。如果您需要在调用unput()之后保留yytext的值(如上面的示例),您必须首先将其复制到其它地方,或者使用%array来构建扫描(参见匹配)。
最后请注意,您不能放回’EOF’以尝试用文件结束符标记输入流。
input()从输入流中读取下一个字符。例如,下面是消耗C注释的一种方法:
%%
"/
" {
int c;

            for ( ; ; )
                {
                while ( (c = input()) != '*' &&
                        c != EOF )
                    ;    /* eat up text of comment */

                if ( c == '*' )
                    {
                    while ( (c = input()) == '*' )
                        ;
                    if ( c == '/' )
                        break;    /* found the end */
                    }

                if ( c == EOF )
                    {
                    error( "EOF in comment" );
                    break;
                    }
                }
            }

(请注意,如果扫描器是使用C++编译的,那么input()将被称为yyinput(),以避免因input的名称而与C++流发生名称冲突。)
YY_FLUSH_BUFFER;刷新扫描器的内部缓冲区,以便下次扫描器尝试匹配标记时,它将首先使用YY_INPUT()重新填充缓冲区(参见生成扫描器)。这个动作是更通用的yy刷新缓冲区的特殊情况;功能,如下所述(参见多输入缓冲区)。
yyterminate()可以用来代替操作中的返回语句。它终止扫描器并向扫描器的调用者返回一个0,表示“全部完成”。默认情况下,当遇到文件结束时也会调用yyterminate()。它是一个宏,可以重新定义。

posted @ 2025-03-08 23:07  xiaobing3314  阅读(43)  评论(0)    收藏  举报