1.Flex整体框架实现
flex 是一个快速词法分析生成器,它可以将用户用正则表达式写的分词匹配模式构造成一个有限状态自动机(一个C函数),目前很多编译器都采用它来生成词法分析器。
下面介绍在Linux环境下使用flex的方法。
首先安装flex:
sudo apt-get update
sudo apt-get install flex
再次检查是否安装成功
flex --version
然后在对应目录下新建文档,根据flex语法写出一个文件并保存后缀为.l
%%
//正则表达式 操作
[0-9]+ printf("?");
# return 0;
. ECHO;
%%
int main(int argc, char* argv[]) {
yylex();
return 0;
}
int yywrap() {
return 1;
}
注意此文件中的 %% 必须在本行的最前面(即 %% 前面不能有任何空格)。
之后在终端输入:
flex xxxxx.l
此时这个flex词法分析器就会生成一个lex.yy.c文件
然后gcc编译:
gcc -o xxxxx lex.yy.c -lfl
最后运行:
./xxxx
这样在终端输入你想输入的东西,会出现神奇的现象(待读者自己发掘)。
2.解析Flex语法
%%
[0-9]+ printf("?");
# return 0;
. ECHO;
%%
flex 模式文件中,%% 和 %% 之间的内容被称为 规则(rules),本文件中每一行都是一条规则,每条规则由 匹配模式(pattern) 和 事件(action) 组成, 模式在前面,用正则表达式表示,事件在后面,即 C 代码。每当一个模式被匹配到时,后面的 C 代码被执行。flex 会将本段内容翻译成一个名为 yylex 的函数。
int main(int argc, char *argv[]) {
yylex();
return 0;
}
int yywrap() { return 1; }
main 函数是程序的入口, flex 会将这些代码原样的复制到 lex.yy.c 文件的最后面。序开始运行后,就开始执行 yylex 函数,然后开始扫描标准输入。当扫描到 # 后, # 被匹配, return 0 被执行, yylex 函数返回到 main 函数,之后程序结束。
因此其完整的输入格式:
%{
Declarations
%}
Definitions
%%
Rules
%%
User subroutines
第 2 段 %} 和 %% 之间的为 定义(Definitions),在这里可以定义正则表达式中的一些名字,可以在 规则(Rules) 段被使用,如本文件中定义了 DIGIT 为 [0-9], 这样在后面可以用 DIGIT 代替这个正则表达式。
输入文件中最后一行的 yywrap 函数的作用是将多个输入文件打包成一个输入,当 yylex 函数读入到一个文件结束(EOF)时,它会向 yywrap 函数询问, yywrap 函数返回 1 的意思是告诉yylex函数后面没有其他输入文件了。
注意:flex 提供的两个全局变量 yytext 和 yyleng,分别用来表示刚刚匹配到的字符串以及它的长度。
3.使用flex对TinyC源文件进行词法分析
%{
#include <stdio.h>
#include <stdlib.h>
#include "token.h"
%}
DIGIT [0-9]
%%
[ \t\n] ; // 忽略空格、制表符和换行符
"+" { printf("<%d, OPERATOR>\n", Y_ADD); }
"-" { printf("<%d, OPERATOR>\n", Y_SUB); }
"*" { printf("<%d, OPERATOR>\n", Y_MUL); }
"/" { printf("<%d, OPERATOR>\n", Y_DIV); }
"%" { printf("<%d, OPERATOR>", Y_MODULO); }
"<" { printf("<%d, OPERATOR>\n", Y_LESS); }
"<=" { printf("<%d, OPERATOR>\n", Y_LESSEQ); }
">" { printf("<%d, OPERATOR>\n", Y_GREAT); }
">=" { printf("<%d, OPERATOR>\n", Y_GREATEQ); }
"!=" { printf("<%d, OPERATOR>\n", Y_NOTEQ); }
"==" { printf("<%d, OPERATOR>\n", Y_EQ); }
"!" { printf("<%d, OPERATOR>\n", Y_NOT); }
"&&" { printf("<%d, OPERATOR>\n", Y_AND); }
"||" { printf("<%d, OPERATOR>\n", Y_OR); }
"=" { printf("<%d, OPERATOR>\n", Y_ASSIGN); }
"(" { printf("<%d, SYMBOL>\n", Y_LPAR); }
")" { printf("<%d, SYMBOL>\n", Y_RPAR); }
"{" { printf("<%d, SYMBOL>\n", Y_LBRACKET); }
"}" { printf("<%d, SYMBOL>\n", Y_RBRACKET); }
"[" { printf("<%d, SYMBOL>\n", Y_LSQUARE); }
"]" { printf("<%d, SYMBOL>\n", Y_RSQUARE); }
"," { printf("<%d, SYMBOL>\n", Y_COMMA); }
";" { printf("<%d, SYMBOL>\n", Y_SEMICOLON); }
"int" { printf("<%d, KEYWORD>\n", Y_INT); }
"void" { printf("<%d, KEYWORD>\n",Y_VOID); }
"const" { printf("<%d, KEYWORD>\n", Y_CONST); }
"if" { printf("<%d, KEYWORD>\n", Y_IF); }
"else" { printf("<%d, KEYWORD>\n", Y_ELSE); }
"while" { printf("<%d, KEYWORD>\n", Y_WHILE); }
"break" { printf("<%d, KEYWORD>\n", Y_BREAK); }
"continue" { printf("<%d, KEYWORD>\n", Y_CONTINUE); }
"return" { printf("<%d, KEYWORD>\n", Y_RETURN); }
[a-zA-Z_][a-zA-Z0-9_]* { printf("<%d, %s>\n", Y_ID,yytext); }
DIGIT+ {printf("<%d, %s>\n",num_INT,yytext);}
"-"[0-9]+ {printf("<%d, %s>\n", num_INT, yytext);}///
[0-9]+"."[0-9]+ {printf("<%d, %s>\n",num_FLOAT,yytext);}
"-"[0-9]+"."[0-9]+ {printf("<%d, %s>\n", num_FLOAT, yytext);}//
"0x"[0-9a-fA-F]+ { printf("<%d, %d>\n", num_INT, (int)strtol(yytext, NULL, 16)); }
"-0x"[0-9a-fA-F]+ { printf("<%d, %d>\n", num_INT, (int)strtol(yytext, NULL, 16)); }///
"//".* { }
"/*.*?*/" { }
. {return yytext[0];}
%%
int yywrap(){
return 1;
}
int main(void)
{
yylex();
return 0;
}
在一系列正则表达式中,用双引号括起来的字符串就是原始字符串,里面的特殊字符是不需要转义的,而双引号本身必须转义(必须用 \” 或 \042 ),这是 flex 中不同于常规的正则表达式的一个特性。
一些定义在 token.h 文件中
#ifndef COMPILER_LAB_TOKEN_H
#define COMPILER_LAB_TOKEN_H
enum yytokentype {
num_INT = 258,
num_FLOAT = 259,
Y_ID = 260,
Y_INT = 261,
Y_VOID = 262,
Y_CONST = 263,
Y_IF = 264,
Y_ELSE = 265,
Y_WHILE = 266,
Y_BREAK = 267,
Y_CONTINUE = 268,
Y_RETURN = 269,
Y_ADD = 270,
Y_SUB = 271,
Y_MUL = 272,
Y_DIV = 273,
Y_MODULO = 274,
Y_LESS = 275,
Y_LESSEQ = 276,
Y_GREAT = 277,
Y_GREATEQ = 278,
Y_NOTEQ = 279,
Y_EQ = 280,
Y_NOT = 281,
Y_AND = 282,
Y_OR = 283,
Y_ASSIGN = 284,
Y_LPAR = 285,
Y_RPAR = 286,
Y_LBRACKET = 287,
Y_RBRACKET = 288,
Y_LSQUARE = 289,
Y_RSQUARE = 290,
Y_COMMA = 291,
Y_SEMICOLON = 292,
Y_FLOAT = 293
};
typedef union _YYLVAL{
int token;
int int_value;
float float_value;
char* id_name;
}_YYLVAL;
extern _YYLVAL yylval;
#endif //COMPILER_LAB_TOKEN_H