4. 识别字符串与字符字面量

任务目标

在词法分析器中增加对字符字面量(单引号)与字符串字面量(双引号)的识别,并正确处理常见转义序列(如 \n、\t、\\、\'、\")。输入一段源码文本,输出对应的 流,每个 包含类型、字面量值和在源码中的位置。

实现方案

采用手工递归下降方式实现一个最小词法分析器。核心逻辑集中在 next_() 函数中:当遇到单引号或双引号时,调用对应的扫描函数,逐字符读取直到匹配的结束引号,期间处理反斜杠开头的转义序列。字符字面量只允许一个字符(转义序列展开后算一个字符),字符串字面量允许任意长度。扫描完成后返回 ,并记录起始行号和列号。

完整代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

typedef enum {
    _CHAR,
    _STRING,
    _EOF,
    _ERROR
} TokenType;

typedef struct {
    TokenType type;
    char *value;      // 字符或字符串的实际内容(转义已展开)
    int line;
    int col;
} ;

static const char *src;
static int pos = 0;
static int line = 1;
static int col = 1;

static void advance() {
    if (src[pos] == '\n') { line++; col = 1; }
    else col++;
    pos++;
}

static char peek() { return src[pos]; }
static char peek_next() { return src[pos + 1]; }

static char decode_escape(char esc) {
    switch (esc) {
        case 'n': return '\n';
        case 't': return '\t';
        case 'r': return '\r';
        case '0': return '\0';
        case '\\': return '\\';
        case '\'': return '\'';
        case '\"': return '\"';
        default: return esc;  // 未知转义按原字符处理
    }
}

static  make_(TokenType type, char *value, int start_line, int start_col) {
     t;
    t.type = type;
    t.value = value;
    t.line = start_line;
    t.col = start_col;
    return t;
}

static  scan_char() {
    int start_line = line, start_col = col;
    advance();  // 跳过开头的单引号

    if (peek() == '\'' || peek() == '\0') {
        return make_(_ERROR, strdup("unterminated char literal"), start_line, start_col);
    }

    char ch;
    if (peek() == '\\') {
        advance();
        if (peek() == '\0') {
            return make_(_ERROR, strdup("unterminated escape"), start_line, start_col);
        }
        ch = decode_escape(peek());
        advance();
    } else {
        ch = peek();
        advance();
    }

    if (peek() != '\'') {
        return make_(_ERROR, strdup("char literal must contain exactly one character"), start_line, start_col);
    }
    advance();  // 跳过结尾的单引号

    char *buf = malloc(2);
    buf[0] = ch;
    buf[1] = '\0';
    return make_(_CHAR, buf, start_line, start_col);
}

static  scan_string() {
    int start_line = line, start_col = col;
    advance();  // 跳过开头的双引号

    size_t cap = 16, len = 0;
    char *buf = malloc(cap);

    while (peek() != '\0' && peek() != '"') {
        if (peek() == '\\') {
            advance();
            if (peek() == '\0') break;
            char c = decode_escape(peek());
            if (len + 1 >= cap) { cap *= 2; buf = realloc(buf, cap); }
            buf[len++] = c;
            advance();
        } else {
            if (len + 1 >= cap) { cap *= 2; buf = realloc(buf, cap); }
            buf[len++] = peek();
            advance();
        }
    }

    if (peek() != '"') {
        free(buf);
        return make_(_ERROR, strdup("unterminated string literal"), start_line, start_col);
    }
    advance();  // 跳过结尾的双引号

    buf[len] = '\0';
    return make_(_STRING, buf, start_line, start_col);
}

 next_() {
    while (isspace(peek())) advance();

    if (peek() == '\0') {
        return make_(_EOF, strdup(""), line, col);
    }

    if (peek() == '\'') return scan_char();
    if (peek() == '"') return scan_string();

    // 其他字符(如标识符、运算符)不在本案例范围内,直接跳过并报错
    char *err = malloc(16);
    snprintf(err, 16, "unexpected char: %c", peek());
    advance();
    return make_(_ERROR, err, line, col - 1);
}

int main() {
    // 测试输入:包含字符、字符串和转义序列
    const char *test_src = "'a' \"hello\\nworld\" '\\'' \"say \\\"hi\\\"\"";
    src = test_src;
    pos = 0; line = 1; col = 1;

     t;
    do {
        t = next_();
        if (t.type == _CHAR) {
            printf("CHAR  '%s'  @%d:%d\n", t.value, t.line, t.col);
        } else if (t.type == _STRING) {
            printf("STRING \"%s\"  @%d:%d\n", t.value, t.line, t.col);
        } else if (t.type == _ERROR) {
            printf("ERROR %s  @%d:%d\n", t.value, t.line, t.col);
        }
        free(t.value);
    } while (t.type != _EOF && t.type != _ERROR);

    return 0;
}

编译与运行

gcc -o lexer lexer.c
./lexer

验证方法

程序内置了测试输入 'a' "hello\nworld" '\'' "say \"hi\"",包含四种典型情况:普通字符、含换行转义的字符串、转义单引号字符、含转义双引号的字符串。运行后观察输出:

  • 第1个 应为 CHAR 'a',位置 @1:1;
  • 第2个 应为 STRING "hello\nworld"(其中 \n 已展开为真实换行符,终端显示为换行),位置 @1:5;
  • 第3个 应为 CHAR '''(单引号字符),位置 @1:22;
  • 第4个 应为 STRING "say \"hi\""(含双引号字符),位置 @1:27;
  • 最后输出 EOF。

若输出与上述一致,说明字符/字符串识别与转义处理均正确。可修改 test_src 中的内容验证更多边界情况(如未闭合引号、空字符串 "" 等),观察错误提示是否符合预期。