3. 实现一个简单的词法分析器
任务说明
实现一个词法分析器,输入为源代码字符串,输出为序列。支持三类:标识符(字母或下划线开头,后跟字母/数字/下划线)、数字(整数或浮点数)、运算符(+ - * / = ( ) { } ;)。忽略空白字符,遇到无法识别的字符时报错。
完整实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
typedef enum {
TOK_IDENT,
TOK_NUMBER,
TOK_OPERATOR,
TOK_EOF,
TOK_ERROR
} TokenType;
typedef struct {
TokenType type;
char lexeme[64];
int line;
} ;
// 运算符表
static const char* operators[] = {"+", "-", "*", "/", "=", "(", ")", "{", "}", ";"};
int is_operator_char(char c) {
const char* ops = "+-*/=(){};";
return strchr(ops, c) != NULL;
}
get_next_(const char** src, int* line) {
tok;
tok.lexeme[0] = '\0';
tok.line = *line;
// 跳过空白
while (**src && isspace(**src)) {
if (**src == '\n') (*line)++;
(*src)++;
}
if (**src == '\0') {
tok.type = TOK_EOF;
return tok;
}
// 标识符
if (isalpha(**src) || **src == '_') {
int i = 0;
while (**src && (isalnum(**src) || **src == '_') && i < 63) {
tok.lexeme[i++] = **src;
(*src)++;
}
tok.lexeme[i] = '\0';
tok.type = TOK_IDENT;
return tok;
}
// 数字(整数或浮点数)
if (isdigit(**src)) {
int i = 0;
while (**src && isdigit(**src) && i < 63) {
tok.lexeme[i++] = **src;
(*src)++;
}
// 浮点数:小数点后至少一位数字
if (**src == '.' && isdigit(*(*src + 1))) {
tok.lexeme[i++] = **src;
(*src)++;
while (**src && isdigit(**src) && i < 63) {
tok.lexeme[i++] = **src;
(*src)++;
}
}
tok.lexeme[i] = '\0';
tok.type = TOK_NUMBER;
return tok;
}
// 运算符
if (is_operator_char(**src)) {
tok.lexeme[0] = **src;
tok.lexeme[1] = '\0';
tok.type = TOK_OPERATOR;
(*src)++;
return tok;
}
// 无法识别的字符
tok.lexeme[0] = **src;
tok.lexeme[1] = '\0';
tok.type = TOK_ERROR;
(*src)++;
return tok;
}
void print_( tok) {
const char* type_names[] = {"IDENT", "NUMBER", "OPERATOR", "EOF", "ERROR"};
printf("line %d: %-8s '%s'\n", tok.line, type_names[tok.type], tok.lexeme);
}
int main() {
const char* source = "x = 42 + 3.14 * (y - 2) / z;";
const char* p = source;
int line = 1;
tok;
printf("源代码: %s\n\n", source);
printf("序列:\n");
while (1) {
tok = get_next_(&p, &line);
print_(tok);
if (tok.type == TOK_EOF || tok.type == TOK_ERROR) break;
}
return 0;
}
编译与运行
gcc -o lexer lexer.c
./lexer
预期输出
源代码: x = 42 + 3.14 * (y - 2) / z;
序列:
line 1: IDENT 'x'
line 1: OPERATOR '='
line 1: NUMBER '42'
line 1: OPERATOR '+'
line 1: NUMBER '3.14'
line 1: OPERATOR '*'
line 1: OPERATOR '('
line 1: IDENT 'y'
line 1: OPERATOR '-'
line 1: NUMBER '2'
line 1: OPERATOR ')'
line 1: OPERATOR '/'
line 1: IDENT 'z'
line 1: OPERATOR ';'
line 1: EOF ''
验证要点
- 标识符
x、y、z被正确识别为IDENT类型 - 整数
42和浮点数3.14均被识别为NUMBER类型,且浮点数包含小数点 - 所有运算符
= + * ( ) - / ;被逐一拆分,每个运算符为独立 - 空白字符被完全跳过,不产生
- 每个记录了正确的行号(本例全部为第1行)
错误处理验证
将main中的source改为"x = @ 5",重新编译运行,应输出:
line 1: IDENT 'x'
line 1: OPERATOR '='
line 1: ERROR '@'
line 1: NUMBER '5'
遇到非法字符@时返回TOK_ERROR,词法分析在遇到ERROR或EOF时终止。