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时终止。

源代码字符串 get_next_() 序列 字符类型判定 字母/下划线 → IDENT 数字 → NUMBER 运算符 → OPERATOR 其他字符 → ERROR 循环直到 EOF 示例输出 IDENT 'x' NUMBER '42' OPERATOR '+'