简述
Flex是重写Lex诞生的快速词法分析生成器,在编译前端(词法分析->语法分析->语义分析)中处在最靠前的位置,它可以用来生成特定的词法分析程序。
安装Flex:
apt-get install flex
没有专用于Flex的IDE,可以在VSCode安装Lex Flex Yacc Bison
插件,可以让Flex语法高亮。
Flex使用示例
Flex程序通常写成.l
文件,其中由两个%%
分成上中下三部分,第一部分是声明和选项设置(编译后被原样写入生成的词法分析程序最顶端),第二部分是正则表达式模式和相应Action,第三部分主程序中是一些与Action相关的例程(也会被照抄)。
第一部分如果不需要就不用写。第三部分如果不写,Flex会提供一个最小的调用词法分析器的主程序。
核心就在第二部分,因为一个正则表达式可以对应一个有限自动机,Flex就是将正则表达式翻译成DFA,然后在DFA转移时执行相应的Action完成处理。
如课本上的统计行数、单词数、字符数的Flex程序:
%{
int chars = 0; //用于记录字符数
int words = 0; //用于记录单词数
int lines = 0; //用于记录行数
%}
%% //分割第一部分和第二部分
[^ \t\n\r\f\v]+ { words++; chars+=strlen(yytext); } //匹配到单词(没有空白符的连续串)时,单词数+1,字符数+=单词长度
\n { chars++; lines++; } //匹配到换行符时,字符数+1,行数+1
. { chars++; } //匹配到其它任何字符,只增加字符数
%% //分割第二部分和第三部分
//主函数
int main(int argc, char **argv)
{
yylex(); //调用Flex提供的词法分析例程yylex()
printf("%8d%8d%8d\n", lines, words, chars); //输出统计结果
}
使用flex
命令对其翻译:
flex WordCount.l
在同一目录下生成了近1800的C语言程序lex.yy.c
,这就是Flex生成的程序,只不过这个例子不是词法分析目的。将这个程序用gcc
(或者Unix的cc
)编译:
gcc lex.yy.c -lfl
其中-lfl
参数用于链接flex的库函数。编译后在目录下生成了a.out
,直接执行就可以使用这个程序了,输入可换行的文本并最终Ctrl+D(这是Unix/Linux下的换行符)结束输入:
lzh@DESKTOP-HCSIG2E:/mnt/e/Compiler/flex$ ./a.out
I am sb lzh.
I like cute cat, and i wanna eat foods.
That's all, bye!
3 16 70
这表示,输入的文本有3行,16个单词("That’s"整个视为一个单词),70个字符。
Flex生成词法分析程序
这里按照课本上的案例,做一个整数的四则运算器。
Flex在这里生成词法分析程序,词法分析程序会识别输入的单词,然后将分析结果输出。
%%
"+" { printf("PLUS\n"); }
"-" { printf("MINUS\n"); }
"*" { printf("TIMES\n"); }
"/" { printf("DIVIDE\n"); }
"|" { printf("ABS\n"); }
[0-9]+ { printf("NUMBER %s\n", yytext); } //数字
\n { printf("NEWLINE\n"); }
[ \t] { } //忽略空白符
. { printf("Mystery charactor %s\n", yytext); } //其它字符是不合法的,提示错误
%%
翻译,编译,运行:
lzh@DESKTOP-HCSIG2E:/mnt/e/Compiler/flex$ ./a.out
2019+11-30/2
NUMBER 2019
PLUS
NUMBER 11
MINUS
NUMBER 30
DIVIDE
NUMBER 2
NEWLINE
77 + 8 8 | abc
NUMBER 77
PLUS
NUMBER 8
NUMBER 8
ABS
Mystery charactor a
Mystery charactor b
Mystery charactor c
NEWLINE
改进的词法分析程序
一般为模式匹配的Action中设置返回值(而不是像前面那样直接print
),这样yylex()
每次识别到相应的模式,如果有返回值,就立即返回,然后继续调用yylex()
识别下一个模式;如果是没有返回值的模式,就会继续向后识别。
另外,还可以设置记号编号和记号值。记号编号用于记录词的类别,而记号的值则用于记录此类别的某个具体值。例如记号编号可以是NUMBER,然后记号值取10,就表示了整型常量10。
Bison创建的语法分析器自动从258开始指派记号编号,这是为了防止和文字字符冲突。Flex里为了和Bison统一,也不妨从258开始编号。
记号的值为了能给不同的类型使用,通常使用union
类型。不过这个例子里因为只有数字需要有记号值,所以直接就用int
类型。
%{
//记号编号
enum yytokentype{
NUMBER = 258,
ADD = 259,
SUB = 260,
MUL = 261,
DIV = 262,
ABS = 263,
EOL = 264
};
//存储记号值
int yylval;
%}
%%
"+" { return ADD; }
"-" { return SUB; }
"*" { return MUL; }
"/" { return DIV; }
"|" { return ABS; }
[0-9]+ { yylval = atoi(yytext); return NUMBER; } //匹配到数字时,将其转为int写入记号值的变量中
\n { return EOL; }
[ \t] { } //忽略空白符
. { printf("Mystery charactor %c\n", *yytext); } //其它字符是不合法的,提示错误
%%
int main(int argc, char **argv) {
int tok;
while(tok=yylex()) { //每次从记号流中匹配出一个记号编号
printf("%d", tok);
if(tok==NUMBER) //如果是数字,还需要输出记号值
printf(" = %d\n", yylval);
else
printf("\n");
}
return 0;
}
翻译,编译,运行:
lzh@DESKTOP-HCSIG2E:/mnt/e/Compiler/flex$ ./a.out
2019+11-30/2
258 = 2019
259
258 = 11
260
258 = 30
262
258 = 2
264