我们的第一个Flex程序
Unix系统(同样也包括像Linux和BSD这样的类Unix系统)自带了一个字数统计(word count)程序,这个程序可以读入一个文件然后报告这个文件的行数、单词数和字符数。flex使我们能够用仅仅十几行就完成这个wc程序。
例:字数统计fbh1.l
/* 正如Unix的wc程序 */
%{
int chars = 0;
int words = 0;
int lines = 0;
%}
%%
[a-zA-Z]+ { words++; chars += strlen(yytext); }
\n { chars++; lines++; }
. { chars++; }
%%
main(int argc, char **argv)
{
yylex();
printf("%8d%8d%8d\n", lines, words, chars);
}
这个程序的大部分内容对于C程序员来说是十分熟悉的,因为这些就是C代码。
flex程序包含以下三个部分,各部分之间通过仅有%%的行来分割。
- 声明和选项设置
- 一系列的模式和动作
- 被拷贝到生成的词法分析器里面的 C代码,它们通常是一些与动作代码相关的例程。
在声明部分,%{和%}之间的代码会被原样照抄到生成的C文件的开头部分。在这个例子里面,它只是用来设定了行数、单词数和字符数的变量。
在第二部分,每个模式处在一行的开头处,接着是模式匹配时所需要执行的C代码。这儿的C代码是用{}括住的一行或者多行语句。(模式必须在行首出现,因为flex认为以空白开始的行都是代码而把它们照抄到生成的C程序中)
这个程序只有三个模式。
- 第一个模式,[a-zA-Z] + ,用来匹配一个单词。在方括号里面的字符串是一种字符类(character class),能够匹配任意一个大小写字母,而+这个符号表示匹配一个或者多个前面的字符类,也就是一连串的字母,或者说一个单词。相关的动作更新匹配过的单词和字符的个数。在任意一个flex的动作中,变量yytext总是被设为指向本次匹配的输入文本。在这个例子里,我们所需要关心的是有多少个字符,因此我们可以借助这个变量来统计字符数。
- 第二个模式,\n,用来匹配换行符。相关的动作更新行数和字符数。
- 第三个模式是一个点号,它在正则表达式中代表任意一个字符(与shell脚本中的?相 似)。关联的动作更新字符数。
末尾的C代码是我们的主程序,它负责调用flex提供的词法分析例程yylex(),并输出结果。在没有任何其他改变的情况下,词法分析器将读取标准输入。我们来运行一下。
$ flex fbh1.l
$ gcc lex.yy.c -lfl
$ ./a.cout
The boy stood on the burning deck
shelling peanuts by the peck
^D
2 12 63
$
首先我们用flex来翻译我们的程序,flex在没有任何错误的情况下默默地完成了翻译,正如Unix的经典设定那样。接着我们编译flex生成的C程序:lex.yy.c;将它与相应的flex 库文件(-Ifl)链接,运行它,然后提供一小段输入以便于程序进行统计。看起来一切运作正常。
注:真正的wc程序对单词的定义和这儿相比有些微小的差别,它的定义是没有空白字符的字符串。一旦我们知道哪些是空白字符,我们只需把匹配单词的那个模式替换成匹配没有空白字符的字符串:
[^ \t\n\r\f\v]+ { words++; chars += strlen(yytext); }
在字符类开始部分的符号^是指匹配任意一个不在字符类里面的字符,而符号+依然意味 着匹配一个或者多个前面的模式。这展示了flex的强大之一,我们很容易在模式上做一些小的改动而让flex去担心它们可能怎样影响生成的代码。