2. 词法分析器flex实战

任务目标

使用flex编写词法分析器,从C语言源文件中识别关键字、标识符、数字和运算符,输出带类型标记的流。输入为单个.c文件,输出逐行打印及其类别。

环境依赖

需要安装flex(Linux: apt install flex / macOS: brew install flex)。无需其他第三方库。

实现步骤

  1. 编写flex规则文件 lexer.l,定义关键字、标识符、数字、运算符的正则模式及对应动作。
  2. 使用flex生成C源码:flex lexer.l,得到 lex.yy.c。
  3. 编译并链接:gcc lex.yy.c -o lexer -lfl。
  4. 运行:./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默认最长匹配,&&会优先于单个&匹配,无需额外处理。