2. 识别标识符与关键字
任务目标
为自制编译器实现词法分析中的标识符与关键字识别模块。输入为源代码字符串,输出为序列,其中每个包含类型(IDENTIFIER或KEYWORD)与词素文本。要求关键字匹配区分大小写,且关键字优先于标识符匹配。
实现方案
采用手工递归下降方式实现词法分析器核心逻辑。定义关键字集合(以C语言常用关键字为例),通过先识别完整标识符,再查关键字表的方式完成分类。标识符规则:以字母或下划线开头,后续字符为字母、数字或下划线。大小写处理:关键字表存储精确形式,匹配时逐字符比较,天然区分大小写。
完整代码
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.h>
#define MAX__LEN 64
#define MAX_TOKENS 256
typedef enum {
_IDENTIFIER,
_KEYWORD,
_UNKNOWN
} TokenType;
typedef struct {
TokenType type;
char lexeme[MAX__LEN];
} ;
// 关键字表(区分大小写)
static const char *keywords[] = {
"int", "float", "return", "if", "else",
"while", "for", "break", "continue", "void"
};
#define KEYWORD_COUNT (sizeof(keywords) / sizeof(keywords[0]))
static bool is_keyword(const char *word) {
for (int i = 0; i < KEYWORD_COUNT; i++) {
if (strcmp(word, keywords[i]) == 0) {
return true;
}
}
return false;
}
// 从src的*pos位置开始识别一个标识符或关键字
// 成功返回true,*保存结果;失败返回false
static bool scan_identifier(const char *src, int *pos, *) {
int start = *pos;
char ch = src[*pos];
// 首字符:字母或下划线
if (!(isalpha(ch) || ch == '_')) {
return false;
}
// 后续字符:字母、数字或下划线
while (isalnum(src[*pos]) || src[*pos] == '_') {
(*pos)++;
}
int len = *pos - start;
if (len >= MAX__LEN) {
len = MAX__LEN - 1; // 截断保护
}
strncpy(->lexeme, src + start, len);
->lexeme[len] = '\0';
// 先识别完整词素,再查关键字表
->type = is_keyword(->lexeme) ? _KEYWORD : _IDENTIFIER;
return true;
}
// 词法分析主函数:扫描src,填充tokens数组,返回数量
int tokenize(const char *src, *tokens, int max_tokens) {
int pos = 0;
int count = 0;
while (src[pos] != '\0' && count < max_tokens) {
// 跳过空白字符
if (isspace(src[pos])) {
pos++;
continue;
}
// 尝试识别标识符/关键字
if (isalpha(src[pos]) || src[pos] == '_') {
if (scan_identifier(src, &pos, &tokens[count])) {
count++;
continue;
}
}
// 无法识别的字符:跳过并标记UNKNOWN
tokens[count].type = _UNKNOWN;
tokens[count].lexeme[0] = src[pos];
tokens[count].lexeme[1] = '\0';
count++;
pos++;
}
return count;
}
// 辅助:打印类型名称
static const char *type_name(TokenType t) {
switch (t) {
case _IDENTIFIER: return "IDENTIFIER";
case _KEYWORD: return "KEYWORD";
default: return "UNKNOWN";
}
}
int main() {
// 测试输入:包含关键字、标识符、大小写变体
const char *source = "int main() { Int value = 10; return value; }";
tokens[MAX_TOKENS];
int n = tokenize(source, tokens, MAX_TOKENS);
printf("输入: %s\n\n", source);
printf("共识别 %d 个:\n", n);
for (int i = 0; i < n; i++) {
printf(" [%s] %s\n", type_name(tokens[i].type), tokens[i].lexeme);
}
return 0;
}
运行与验证
编译:
gcc -o lexer lexer.c
运行:
./lexer
预期输出(确定性结果):
输入: int main() { Int value = 10; return value; }
共识别 12 个:
[KEYWORD] int
[IDENTIFIER] main
[UNKNOWN] (
[UNKNOWN] )
[UNKNOWN] {
[IDENTIFIER] Int
[IDENTIFIER] value
[UNKNOWN] =
[UNKNOWN] 1
[UNKNOWN] 0
[KEYWORD] return
[IDENTIFIER] value
判定标准:
- 关键字
int、return被识别为KEYWORD; - 标识符
main、value被识别为IDENTIFIER; - 输入中的
Int(首字母大写)被识别为IDENTIFIER而非KEYWORD,证明大小写区分生效; - 数字、括号、运算符等非标识符字符被标记为
UNKNOWN,不影响核心逻辑验证。
关键说明
本实现的核心策略是最长匹配后查表:先完整扫描出标识符形式的词素,再与关键字表精确比较。这避免了逐字符判断时可能出现的“将intx误判为关键字”问题。关键字表仅包含小写形式,因此Int、INT等变体自然落入标识符类别。若需扩展关键字集合,直接修改keywords数组即可,无需改动识别逻辑。