词法分析程序flex
基本格式
%{
C语言声明,一般声明全局变量和函数,会复制进lex.yy.c中
%}
定义正则表达式的名字,可以在规则段中使用
%%
规则段,每一行都是一条规则,每一条规则由匹配模式和事件组成。每当一个模式被匹配到,后面的事件被执行!
%%
用户自定义过程,直接复制到lex.yy.c末尾
使用详解
%{
#define T_WORD 1
int numChars = 0, numWords = 0, numLines = 0;
%}
WORD ([^ \t\n\r\a]+)
%%
\n { numLines++; numChars++; }
{WORD} { numWords++; numChars += yyleng; return T_WORD; }
<<EOF>> { return 0; }
. { numChars++; }
%%
int main() {
int token_type;
while (token_type = yylex()) {
printf("WORD:\t%s\n", yytext);
}
printf("\nChars\tWords\tLines\n");
printf("%d\t%d\t%d\n", numChars, numWords, numLines);
return 0;
}
int yywrap() {
return 1;
}
1.声明(Declarations)
%{ 和 %} 之间的为 声明(Declarations)。
%{
#define T_WORD 1
int numChars = 0, numWords = 0, numLines = 0;
%}
都是 C 代码,这些代码会被原样的复制到 lex.yy.c 文件中,一般在这里声明一些全局变量和函数,这样在后面可以使用这些变量和函数。
2.定义
%} 和 %% 之间的为 定义(Definitions)
WORD ([^ \t\n\r\a]+)
在这里可以定义正则表达式中的一些名字,可以在 规则(Rules) 段被使用,如本文件中定义了 WORD 为 ( [ ^ \t\n\r\a] +) , 这样在后面可以用 {WORD} 代替这个正则表达式。
3.规则(rules)
%% 和 %% 之间的内容被称为 规则(rules)
%%
\n { numLines++; numChars++; }
{WORD} { numWords++; numChars += yyleng; return T_WORD; }
<<EOF>> { return 0; }
. { numChars++; }
%%
flex 会将本段内容翻译成一个名为 yylex 的函数,该函数的作用就是扫描输入文件(默认情况下为标准输入)。如果没有匹配到 return 语句,则执行完这些 C 代码后, yylex 函数会继续运行,开始下一轮的扫描和匹配。
4. 用户定义过程(User subroutines)
flex 会将这些代码原样的复制到 lex.yy.c 文件的最后面。
int main() {
int token_type;
while (token_type = yylex()) {
printf("WORD:\t%s\n", yytext);
}
printf("\nChars\tWords\tLines\n");
printf("%d\t%d\t%d\n", numChars, numWords, numLines);
return 0;
}
int yywrap() {
return 1;
}
yywrap 函数
输入文件中最后一行的 yywrap 函数的作用是将多个输入文件打包成一个输入,当 yylex 函数读入到一个文件结束(EOF)时,它会向 yywrap 函数询问。
yywrap 函数返回 1 的意思是告诉 yylex 函数后面没有其他输入文件了,此时 yylex 函数结束,yywrap 函数也可以打开下一个输入文件,再向 yylex 函数返回 0 ,告诉它后面还有别的输入文件,此时 yylex 函数会继续解析下一个输入文件。总之,由于我们不考虑连续解析多个文件,因此此处返回 1 。
以上 4 段中,除了 Rules 段是必须要有的外,其他三个段都是可选的。
本例中的 action 中有 return 语句,而 main 函数内是一个 while 循环,只要 yylex 函数的返回值不为 0 ,则 yylex 函数将被继续调用,此时将从下一个字符开始新一轮的扫描。
本例中使用到了 flex 提供的两个全局变量 yytext 和 yyleng,分别用来表示刚刚匹配到的字符串以及它的长度。
使用 flex 对 TinyC 源文件进行词法分析
步骤
-
列出 TinyC 中所有类型的 token;
-
为每种类型的 token 分配一个唯一的编号,同时写出此 token 的正则表达式;
-
写出每种 token 的 rule (相应的 pattern 和 action )。
1. 列出 TinyC 中所有类型的 token;
TinyC 中的 token 的种类非常少,按其词法特性,分为以下三大类。
- 第 1 类为单字符运算符,一共 15 种:
+ * - / % = , ; ! < > ( ) { } - 第 2 类为双字符运算符和关键字,一共 16 种:
<=, >=, ==, !=, &&, ||
void, int, while, if, else, return, break, continue, print, readint - 第 3 类为整数常量、字符串常量和标识符(变量名和函数名),一共 3 种。
/* Definitions, note: \042 is '"' */
INTEGER ([0-9]+)
UNTERM_STRING (\042[^\042\n]*)
STRING (\042[^\042\n]*\042)
IDENTIFIER ([_a-zA-Z][_a-zA-Z0-9]*)
OPERATOR ([+*-/%=,;!<>(){}])
SINGLE_COMMENT1 ("//"[^\n]*)
SINGLE_COMMENT2 ("#"[^\n]*)
2. 为每种类型的 token 分配一个唯一的编号,同时写出此 token 的正则表达式;
token 的编号原则为:单字符运算符的 token 编号就是其字符的数值,其他类型的 token 则从 256 开始编号。
token.h
#ifndef TOKEN_H
#define TOKEN_H
typedef enum {
T_Le = 256, T_Ge, T_Eq, T_Ne, T_And, T_Or, T_IntConstant,
T_StringConstant, T_Identifier, T_Void, T_Int, T_While,
T_If, T_Else, T_Return, T_Break, T_Continue, T_Print,
T_ReadInt
} TokenType;
static void print_token(int token) {
static char* token_strs[] = {
"T_Le", "T_Ge", "T_Eq", "T_Ne", "T_And", "T_Or", "T_IntConstant",
"T_StringConstant", "T_Identifier", "T_Void", "T_Int", "T_While",
"T_If", "T_Else", "T_Return", "T_Break", "T_Continue", "T_Print",
"T_ReadInt"
};
if (token < 256) {
printf("%-20c", token);//-20表示输出长度为20,左对齐,右边19个为空格。
} else {
printf("%-20s", token_strs[token-256]);//-20表示左对齐,输出长度为20。
}
}
#endif
3. 写出每种 token 的 rule (相应的 pattern 和 action )
省略。。。
完整框架
%{
#include "token.h"
int cur_line_num = 1;
void init_scanner();
void lex_error(char* msg, int line);
%}
/* Definitions, note: \042 is '"' */
INTEGER ([0-9]+)
UNTERM_STRING (\042[^\042\n]*)
STRING (\042[^\042\n]*\042)
IDENTIFIER ([_a-zA-Z][_a-zA-Z0-9]*)
OPERATOR ([+*-/%=,;!<>(){}])
SINGLE_COMMENT1 ("//"[^\n]*)
SINGLE_COMMENT2 ("#"[^\n]*)
%%
[\n] { cur_line_num++; }
[ \t\r\a]+ { /* ignore all spaces */ }
{SINGLE_COMMENT1} { /* skip for single line comment */ }
{SINGLE_COMMENT2} { /* skip for single line commnet */ }
{OPERATOR} { return yytext[0]; }
"<=" { return T_Le; }
">=" { return T_Ge; }
"==" { return T_Eq; }
"!=" { return T_Ne; }
"&&" { return T_And; }
"||" { return T_Or; }
"void" { return T_Void; }
"int" { return T_Int; }
"while" { return T_While; }
"if" { return T_If; }
"else" { return T_Else; }
"return" { return T_Return; }
"break" { return T_Break; }
"continue" { return T_Continue; }
"print" { return T_Print; }
"readint" { return T_ReadInt; }
{INTEGER} { return T_IntConstant; }
{STRING} { return T_StringConstant; }
{IDENTIFIER} { return T_Identifier; }
<<EOF>> { return 0; }
{UNTERM_STRING} { lex_error("Unterminated string constant", cur_line_num); }
. { lex_error("Unrecognized character", cur_line_num); }
%%
int main(int argc, char* argv[]) {
int token;
init_scanner();
while (token = yylex()) {
print_token(token);
puts(yytext);
}
return 0;
}
void init_scanner() {
printf("%-20s%s\n", "TOKEN-TYPE", "TOKEN-VALUE");
printf("-------------------------------------------------\n");
}
void lex_error(char* msg, int line) {
printf("\nError at line %-3d: %s\n\n", line, msg);
}
int yywrap(void) {
return 1;
}
参考
自己动手写编译器[https://pandolia.net/tinyc/ch8_flex.html]
flex test.l //此后会生成C文件lex.yy.c
gcc lex.yy.c //使用gcc编译成可执行文件