2. 词法分析器flex实战
任务目标
使用flex编写词法分析器,从C语言源文件中识别关键字、标识符、数字和运算符,输出带类型标记的流。输入为单个.c文件,输出逐行打印及其类别。
环境依赖
需要安装flex(Linux: apt install flex / macOS: brew install flex)。无需其他第三方库。
实现步骤
- 编写flex规则文件
lexer.l,定义关键字、标识符、数字、运算符的正则模式及对应动作。 - 使用flex生成C源码:
flex lexer.l,得到lex.yy.c。 - 编译并链接:
gcc lex.yy.c -o lexer -lfl。 - 运行:
./lexer input.c,观察输出流。
完整代码(lexer.l)
%{
#include <stdio.h>
#include <string.h>
// 类型定义
enum {
T_KEYWORD = 1,
T_IDENT,
T_NUMBER,
T_OPERATOR,
T_UNKNOWN
};
// 关键字表
const char* keywords[] = {
"auto","break","case","char","const","continue","default","do",
"double","else","enum","extern","float","for","goto","if",
"int","long","register","return","short","signed","sizeof","static",
"struct","switch","typedef","union","unsigned","void","volatile","while"
};
#define NUM_KEYWORDS (sizeof(keywords)/sizeof(keywords[0]))
int is_keyword(const char* s) {
for (int i = 0; i < NUM_KEYWORDS; i++) {
if (strcmp(s, keywords[i]) == 0) return 1;
}
return 0;
}
%}
%option noyywrap
%%
[ \t\n]+ { /* 跳过空白 */ }
"/*"([^*]|\*+[^*/])*\*+"/" { /* 跳过注释 */ }
"//"[^\n]* { /* 跳过行注释 */ }
[a-zA-Z_][a-zA-Z0-9_]* {
if (is_keyword(yytext)) {
printf("KEYWORD : %s\n", yytext);
} else {
printf("IDENTIFIER: %s\n", yytext);
}
}
[0-9]+(\.[0-9]+)?([eE][+-]?[0-9]+)? {
printf("NUMBER : %s\n", yytext);
}
"+"|"-"|"*"|"/"|"%"|"&&"|"||"|"!"|"<"|">"|"<="|">="|"=="|"!="|"="|"++"|"--"|"+"|"-" {
printf("OPERATOR : %s\n", yytext);
}
. { printf("UNKNOWN : %s (0x%02X)\n", yytext, yytext[0]); }
%%
int main(int argc, char* argv[]) {
if (argc != 2) {
fprintf(stderr, "用法: %s <源文件.c>\n", argv[0]);
return 1;
}
FILE* fp = fopen(argv[1], "r");
if (!fp) {
perror("无法打开文件");
return 1;
}
yyin = fp;
yylex();
fclose(fp);
return 0;
}
验证方法
创建测试文件 test.c:
int main() {
int count = 10;
double pi = 3.14;
if (count > 5) {
count = count + 1;
}
return 0;
}
运行 ./lexer test.c,预期输出应包含:
KEYWORD : int
IDENTIFIER: main
OPERATOR : (
OPERATOR : )
OPERATOR : {
KEYWORD : int
IDENTIFIER: count
OPERATOR : =
NUMBER : 10
...
通过条件:所有C关键字均标记为KEYWORD,变量名标记为IDENTIFIER,数字常量(含浮点)标记为NUMBER,运算符标记为OPERATOR,注释和空白被正确跳过,无UNKNOWN输出(除非源文件含非法字符)。
常见问题
- 编译报错
undefined reference to yylex:确认链接时加了-lfl标志。 - 关键字未被识别:检查关键字表是否完整,flex规则中标识符模式需在关键字判断之前匹配(当前实现通过
is_keyword函数在动作中判断,顺序无关)。 - 运算符匹配冲突:flex默认最长匹配,
&&会优先于单个&匹配,无需额外处理。