神图镇楼 ,2w行手写代码见证总结、归纳、开源化过程:
代码文件夹的内容:
首先,开头附上神图一张,这是为什么本人手撸完可能是全网最适合初学者学习的c语言教程之后,收录了将近两万行代码,突然心血来潮搞这么一出编译器、手撸os、手撸最小化系统、手撸100例趣味例程:
语法、100例子、牛客大厂题目单100、课程自学到现在
其实也就是我个人技术博客的一个延申和mark见证,两个月的时间写了将近两万行c,从语法到大场面试到最终的自学c语言所有常见考点,再写这么一个超级硬核超级底层的内容,也更多是为了给自己一个挑战
以上:引子
————————————————
彻底掀开C语言的“底裤”:手撸编译器系列(一)—— 词法分析的惊天秘密!
引言:C语言为什么是YYDS?编译器的“黑箱”之谜!
兄弟们,咱们先来聊聊C语言。为啥这么多年过去了,各种新潮语言层出不穷,C语言依然稳坐“编程语言鄙视链”的顶端,被无数高手奉为“永远的神”(YYDS)?
因为C语言,它真的“够底层”!它能直接操作内存,能和操作系统“亲密接触”,能写出性能爆炸的代码,是操作系统、嵌入式、高性能计算等领域的绝对王者。但与此同时,它的“底层”也让很多人望而却步,尤其是它背后那个默默无闻、却又至关重要的“黑箱”——编译器。
你是不是也曾疑惑:
-
我写的C代码,比如一个简单的
int a = 10;
,它是怎么变成CPU能执行的010101...
这样的机器指令的? -
编译器到底是个什么“妖魔鬼怪”,能把人类能懂的文字,变成机器能懂的二进制?
-
为什么那么多C语言程序员,一辈子都在用编译器,却从来没想过去了解它,甚至去“手撸”一个?
今天,我就是要打破这个“黑箱”,带你深入编译器的腹地,亲手触摸它的脉搏。咱们要做的,就是从最原始的字符开始,一步步构建一个能把C语言代码变成机器能执行的程序的神器!
这绝对不是什么“屠龙之术”,而是每一个想真正吃透C语言、想深入计算机底层原理的程序员都应该掌握的“葵花宝典”!
编译器的“庐山真面目”:它到底是个啥玩意儿?
在咱们开始“手撸”之前,先来个高屋建瓴的概览。编译器,说白了,就是一个翻译官。它把一种编程语言(比如C语言)写的程序,翻译成另一种语言(比如机器语言或者汇编语言)写的程序。
整个翻译过程,就像咱们做饭一样,它不是一蹴而就的,而是分成好几个“工序”:
(这里想象一个思维导图:中心是“编译器”,分支出去是各个阶段)
-
预处理(Preprocessing):这步不是编译器做的,但它是编译的第一个环节。它会处理
#include
、#define
等预处理指令,把宏展开,把头文件内容包含进来,生成一个“干净”的C代码文件。 -
编译(Compilation):这才是编译器真正开始干活的地方!它把预处理后的C代码翻译成汇编语言。这步又细分为好几个子阶段,咱们今天主要就围绕它展开:
-
词法分析(Lexical Analysis):把源代码的字符流分解成一个个有意义的“单词”(Token)。
-
语法分析(Syntax Analysis):根据语言的语法规则,把这些“单词”组织成有意义的“句子”(语法树)。
-
语义分析(Semantic Analysis):检查这些“句子”的含义是否合法,比如变量有没有定义、类型是否匹配等。
-
中间代码生成(Intermediate Code Generation):把语法树转换成一种更接近机器语言,但又独立于具体机器的“中间代码”。
-
代码优化(Code Optimization):对中间代码进行各种优化,让生成的程序跑得更快、占用内存更少。
-
目标代码生成(Target Code Generation):把优化后的中间代码翻译成特定机器的汇编语言。
-
-
汇编(Assembly):汇编器(Assembler)登场!它把汇编语言翻译成机器码(目标文件,
.o
或.obj
)。 -
链接(Linking):链接器(Linker)出马!它把多个目标文件以及程序用到的库文件(比如
printf
函数所在的库)组合起来,生成一个最终的可执行文件。
(这里想象一个流程图:C Source -> Preprocessor -> Clean C Code -> Compiler (Lexical Analysis -> Syntax Analysis -> Semantic Analysis -> Intermediate Code Gen -> Code Optimization -> Target Code Gen) -> Assembly Code -> Assembler -> Object File -> Linker -> Executable File)
是不是有点懵?没关系!咱们今天就聚焦在编译器的第一步,也是最基础、最直观的一步——词法分析!
词法分析:编译器的“眼睛”——把代码“大卸八块”!
想象一下,你拿到一本英文书,要把它翻译成中文。你第一步会做什么?是不是先把书上的文字一个一个地“读”出来,识别出每个单词,比如 Hello
、World
、is
、a
、book
?你不会一开始就去分析句子的主谓宾定状补,对吧?
词法分析器(Lexical Analyzer),也叫扫描器(Scanner)或者分词器(Tokenizer),就是编译器的“眼睛”,它干的就是这个活儿!
它的任务非常简单粗暴,但又极其重要:
把你的C语言源代码(一串长长的字符流),分解成一个个有独立意义的最小单位——“词法单元”(Token)。
这些“词法单元”就像咱们语言里的“单词”一样,它们是语法分析的基础。
什么是“词法单元”(Token)?
一个“词法单元”(Token)通常包含两个部分:
-
类型(Type):这个“单词”属于什么种类?是关键字?标识符?运算符?还是常量?
-
值(Value/Lexeme):这个“单词”具体是什么?比如,如果类型是“标识符”,值可能是“main”;如果类型是“整数常量”,值可能是“100”。
咱们举个栗子:
C语言代码:int main() { return 0; }
经过词法分析后,它可能会被分解成这样一串Token流:
Token 类型 |
Token 值(词素) |
描述 |
---|---|---|
|
|
关键字 |
|
|
标识符 |
|
|
左括号 |
|
|
右括号 |
|
|
左大括号 |
|
|
关键字 |
|
|
整数常量 |
|
|
分号 |
|
|
右大括号 |
|
|
文件结束符(表示所有代码都已处理完毕) |
看到了吗?源代码中的空格、换行、注释等“不重要”的字符,在词法分析阶段就会被无情地抛弃!因为它们对程序的语义没有直接影响。
词法分析器的工作原理:有限状态机(FSM)
词法分析器是如何识别出这些Token的呢?它通常是基于**有限状态机(Finite State Machine, FSM)**来实现的。
想象一下,词法分析器就像一个“读心术”大师,它一个字符一个字符地读取源代码,根据当前的字符和它之前“读到”的字符,来判断自己处于什么“状态”,并决定接下来要识别什么类型的Token。
(这里想象一个简化的状态机图:
-
初始状态 -> 读到字母 -> 进入“标识符/关键字”状态
-
“标识符/关键字”状态 -> 读到字母/数字 -> 保持当前状态
-
“标识符/关键字”状态 -> 读到非字母数字 -> 识别出标识符/关键字,回到初始状态
-
初始状态 -> 读到数字 -> 进入“数字”状态
-
“数字”状态 -> 读到数字 -> 保持当前状态
-
“数字”状态 -> 读到非数字 -> 识别出数字,回到初始状态
-
初始状态 -> 读到
+
-> 识别出+
,回到初始状态 -
初始状态 -> 读到
=
-> 进入“等号”状态 -
“等号”状态 -> 读到
=
-> 识别出==
,回到初始状态 -
“等号”状态 -> 读到非
=
-> 识别出=
,回到初始状态 -
...以此类推,处理各种Token)
这个过程,就是不断地匹配模式。比如:
-
标识符/关键字:由字母或下划线开头,后面跟着字母、数字或下划线。
-
数字常量:由数字组成。
-
字符串常量:由双引号
"
包裹起来的字符序列。 -
运算符:
+
,-
,*
,/
,==
,!=
,&&
,||
等。 -
分隔符:
(
,)
,{
,}
,;
,,
等。
手撸一个简易C语言词法分析器:让理论照进现实!
光说不练假把式!现在,咱们就来亲手写一个简易的C语言词法分析器。这个词法分析器能够识别C语言中的:
-
关键字:
int
,return
,if
,else
,while
,for
等(咱们先实现几个常用的)。 -
标识符:变量名、函数名等。
-
整数常量:比如
123
,0
。 -
字符串常量:比如
"hello"
,"world"
。 -
运算符:
+
,-
,*
,/
,=
,==
,!=
,>
,<
,>=
,<=
,&&
,||
,!
,&
,|
,^
,~
,<<
,>>
等。 -
分隔符:
(
,)
,{
,}
,[
,]
,;
,,
。 -
注释:单行注释
//
和多行注释/* ... */
。 -
空白字符:空格、制表符、换行符。
咱们的目标是,给它一段C代码,它能吐出一串Token流。
核心思路与数据结构
-
源代码管理:我们需要一个机制来读取源代码,一个字符一个字符地读,并且能“偷看”下一个字符。
-
Token结构:定义一个结构体来表示一个Token,包含类型和值。
-
Token类型枚举:用一个枚举类型来定义所有可能的Token类型。
-
关键字表:用一个数据结构(比如数组或哈希表)来存储C语言的关键字,方便快速查找。
代码实现:lexer.h
和 lexer.c
为了代码的清晰和模块化,我们将词法分析器的代码分成两个文件:lexer.h
(头文件,定义接口和数据结构) 和 lexer.c
(源文件,实现具体逻辑)。
lexer.h
:定义Token类型和结构
#ifndef LEXER_H
#define LEXER_H
#include <stdio.h> // For FILE, EOF
#include <stdlib.h> // For malloc, free
#include <string.h> // For strdup, strcmp
#include <stdbool.h> // For bool type
// 定义所有可能的词法单元类型(Token Type)
// 这是一个枚举,每个成员代表一种Token类型
typedef enum {
// 特殊类型
TOKEN_EOF = 0, // 文件结束符 (End Of File)
TOKEN_UNKNOWN, // 未知或非法字符
// 关键字 (Keywords)
TOKEN_KEYWORD_INT, // int
TOKEN_KEYWORD_RETURN, // return
TOKEN_KEYWORD_IF, // if
TOKEN_KEYWORD_ELSE, // else
TOKEN_KEYWORD_WHILE, // while
TOKEN_KEYWORD_FOR, // for
TOKEN_KEYWORD_VOID, // void
TOKEN_KEYWORD_CHAR, // char
TOKEN_KEYWORD_SHORT, // short
TOKEN_KEYWORD_LONG, // long
TOKEN_KEYWORD_FLOAT, // float
TOKEN_KEYWORD_DOUBLE, // double
TOKEN_KEYWORD_STRUCT, // struct
TOKEN_KEYWORD_UNION, // union
TOKEN_KEYWORD_ENUM, // enum
TOKEN_KEYWORD_TYPEDEF, // typedef
TOKEN_KEYWORD_SIZEOF, // sizeof
TOKEN_KEYWORD_BREAK, // break
TOKEN_KEYWORD_CONTINUE, // continue
TOKEN_KEYWORD_SWITCH, // switch
TOKEN_KEYWORD_CASE, // case
TOKEN_KEYWORD_DEFAULT, // default
TOKEN_KEYWORD_DO, // do
TOKEN_KEYWORD_GOTO, // goto
TOKEN_KEYWORD_CONST, // const
TOKEN_KEYWORD_VOLATILE, // volatile
TOKEN_KEYWORD_EXTERN, // extern
TOKEN_KEYWORD_STATIC, // static
TOKEN_KEYWORD_AUTO, // auto
TOKEN_KEYWORD_REGISTER, // register
TOKEN_KEYWORD_SIGNED, // signed
TOKEN_KEYWORD_UNSIGNED, // unsigned
// 标识符 (Identifiers)
TOKEN_IDENTIFIER, // 变量名、函数名等
// 常量 (Literals)
TOKEN_INT_LITERAL, // 整数常量 (e.g., 123, 0)
TOKEN_STRING_LITERAL, // 字符串常量 (e.g., "hello world")
// TOKEN_FLOAT_LITERAL, // 浮点数常量 (暂时不实现,后续可扩展)
// TOKEN_CHAR_LITERAL, // 字符常量 (暂时不实现,后续可扩展)
// 运算符 (Operators) - 单字符
TOKEN_PLUS, // +
TOKEN_MINUS, // -
TOKEN_STAR, // *
TOKEN_SLASH, // /
TOKEN_MODULO, // %
TOKEN_ASSIGN, // =
TOKEN_LT, // <
TOKEN_GT, // >
TOKEN_AMPERSAND, // & (位与 或 地址运算符)
TOKEN_PIPE, // | (位或)
TOKEN_CARET, // ^ (位异或)
TOKEN_TILDE, // ~ (位非)
TOKEN_EXCLAMATION, // ! (逻辑非)
TOKEN_DOT, // . (成员访问)
TOKEN_COMMA, // , (逗号)
TOKEN_COLON, // : (冒号)
TOKEN_QUESTION, // ? (三目运算符)
// 运算符 (Operators) - 双字符
TOKEN_EQ, // == (等于)
TOKEN_NE, // != (不等于)
TOKEN_LE, // <= (小于等于)
TOKEN_GE, // >= (大于等于)
TOKEN_AND, // && (逻辑与)
TOKEN_OR, // || (逻辑或)
TOKEN_INC, // ++ (自增)
TOKEN_DEC, // -- (自减)
TOKEN_PLUS_ASSIGN, // +=
TOKEN_MINUS_ASSIGN, // -=
TOKEN_STAR_ASSIGN, // *=
TOKEN_SLASH_ASSIGN, // /=
TOKEN_MODULO_ASSIGN, // %=
TOKEN_LSHIFT, // << (左移)
TOKEN_RSHIFT, // >> (右移)
TOKEN_LSHIFT_ASSIGN, // <<=
TOKEN_RSHIFT_ASSIGN, // >>=
TOKEN_AMPERSAND_ASSIGN, // &=
TOKEN_PIPE_ASSIGN, // |=
TOKEN_CARET_ASSIGN, // ^=
TOKEN_ARROW, // -> (成员访问)
// 分隔符 (Delimiters)
TOKEN_LPAREN, // (
TOKEN_RPAREN, // )
TOKEN_LBRACE, // {
TOKEN_RBRACE, // }
TOKEN_LBRACKET, // [
TOKEN_RBRACKET, // ]
TOKEN_SEMICOLON, // ;
} TokenType;
// Token结构体定义
// 每个Token都包含其类型和对应的字符串值(词素)
typedef struct {
TokenType type; // Token的类型
char *value; // Token的字符串值(词素),如 "main", "123", "+"
int line; // Token所在的行号
int column; // Token所在的列号
} Token;
// 全局的源代码指针和当前字符
// current_char_g: 当前正在处理的字符
// source_code_g: 存储整个源代码的字符串
// current_pos_g: 当前字符在source_code_g中的位置
// current_line_g: 当前字符所在的行号
// current_column_g: 当前字符所在的列号
extern char current_char_g;
extern const char *source_code_g;
extern int current_pos_g;
extern int current_line_g;
extern int current_column_g;
// 词法分析器初始化函数
// 参数:filePath - C语言源代码文件的路径
// 返回值:成功返回true,失败返回false
bool lexer_init(const char *filePath);
// 获取下一个词法单元(Token)
// 这是词法分析器的核心函数,每次调用都会返回一个Token
// 返回值:指向新分配的Token的指针,需要调用者负责释放
Token *lexer_getToken();
// 释放Token内存的辅助函数
void free_token(Token *token);
// 词法分析器清理函数
void lexer_cleanup();
#endif // LEXER_H
lexer.c
:词法分析器的核心实现
#include "lexer.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h> // For isalnum, isalpha, isdigit, isspace
// 全局变量的定义
char current_char_g;
const char *source_code_g = NULL;
int current_pos_g = 0;
int current_line_g = 1;
int current_column_g = 1;
// -----------------------------------------------------------------------------
// 辅助函数:字符处理
// -----------------------------------------------------------------------------
// 读取下一个字符并更新位置信息
static void advance() {
// 如果当前位置已经到达源代码末尾,则将当前字符设为EOF
if (source_code_g[current_pos_g] == '\0') {
current_char_g = EOF;
} else {
// 读取当前位置的字符
current_char_g = source_code_g[current_pos_g];
// 更新列号
current_column_g++;
// 如果是换行符,则更新行号和列号
if (current_char_g == '\n') {
current_line_g++;
current_column_g = 1; // 列号重置为1
}
// 移动到下一个字符
current_pos_g++;
}
}
// 偷看下一个字符,但不移动当前位置
static char peek() {
// 如果下一个位置已经到达源代码末尾,则返回EOF
if (source_code_g[current_pos_g] == '\0') {
return EOF;
}
// 返回下一个字符
return source_code_g[current_pos_g];
}
// 检查字符是否是空白字符(空格、制表符、换行符、回车符等)
static bool is_whitespace(char c) {
return isspace(c);
}
// 检查字符是否是字母(包括下划线)
static bool is_alpha(char c) {
return isalpha(c) || c == '_';
}
// 检查字符是否是数字
static bool is_digit(char c) {
return isdigit(c);
}
// 检查字符是否是字母或数字(包括下划线)
static bool is_alphanumeric(char c) {
return isalnum(c) || c == '_';
}
// -----------------------------------------------------------------------------
// Token创建函数
// -----------------------------------------------------------------------------
// 创建一个新的Token并分配内存
// 参数:type - Token的类型
// value - Token的字符串值(词素)
// line - Token所在的行号
// column - Token所在的列号
// 返回值:指向新分配的Token的指针
static Token *create_token(TokenType type, const char *value, int line, int column) {
Token *token = (Token *)malloc(sizeof(Token));
if (!token) {
fprintf(stderr, "Error: Failed to allocate memory for token.\n");
exit(EXIT_FAILURE); // 内存分配失败,直接退出
}
token->type = type;
token->value = value ? strdup(value) : NULL; // 复制字符串值,避免悬空指针
token->line = line;
token->column = column;
return token;
}
// 释放Token内存
void free_token(Token *token) {
if (token) {
free(token->value); // 释放Token值字符串的内存
free(token); // 释放Token结构体本身的内存
}
}
// -----------------------------------------------------------------------------
// 关键字查找表
// -----------------------------------------------------------------------------
// 结构体用于存储关键字及其对应的TokenType
typedef struct {
const char *keyword;
TokenType type;
} KeywordEntry;
// C语言关键字表
// 这是一个静态常量数组,存储了所有我们支持的C语言关键字
static const KeywordEntry keywords[] = {
{"int", TOKEN_KEYWORD_INT},
{"return", TOKEN_KEYWORD_RETURN},
{"if", TOKEN_KEYWORD_IF},
{"else", TOKEN_KEYWORD_ELSE},
{"while", TOKEN_KEYWORD_WHILE},
{"for", TOKEN_KEYWORD_FOR},
{"void", TOKEN_KEYWORD_VOID},
{"char", TOKEN_KEYWORD_CHAR},
{"short", TOKEN_KEYWORD_SHORT},
{"long", TOKEN_KEYWORD_LONG},
{"float", TOKEN_KEYWORD_FLOAT},
{"double", TOKEN_KEYWORD_DOUBLE},
{"struct", TOKEN_KEYWORD_STRUCT},
{"union", TOKEN_KEYWORD_UNION},
{"enum", TOKEN_KEYWORD_ENUM},
{"typedef", TOKEN_KEYWORD_TYPEDEF},
{"sizeof", TOKEN_KEYWORD_SIZEOF},
{"break", TOKEN_KEYWORD_BREAK},
{"continue", TOKEN_KEYWORD_CONTINUE},
{"switch", TOKEN_KEYWORD_SWITCH},
{"case", TOKEN_KEYWORD_CASE},
{"default", TOKEN_KEYWORD_DEFAULT},
{"do", TOKEN_KEYWORD_DO},
{"goto", TOKEN_KEYWORD_GOTO},
{"const", TOKEN_KEYWORD_CONST},
{"volatile", TOKEN_KEYWORD_VOLATILE},
{"extern", TOKEN_KEYWORD_EXTERN},
{"static", TOKEN_KEYWORD_STATIC},
{"auto", TOKEN_KEYWORD_AUTO},
{"register", TOKEN_KEYWORD_REGISTER},
{"signed", TOKEN_KEYWORD_SIGNED},
{"unsigned", TOKEN_KEYWORD_UNSIGNED},
{NULL, TOKEN_UNKNOWN} // 哨兵值,表示列表结束
};
// 查找给定的字符串是否是关键字
// 参数:identifier - 待查找的字符串
// 返回值:如果是关键字,返回对应的TokenType;否则返回TOKEN_IDENTIFIER
static TokenType lookup_keyword(const char *identifier) {
for (int i = 0; keywords[i].keyword != NULL; ++i) {
if (strcmp(identifier, keywords[i].keyword) == 0) {
return keywords[i].type; // 找到匹配的关键字
}
}
return TOKEN_IDENTIFIER; // 不是关键字,则是一个普通的标识符
}
// -----------------------------------------------------------------------------
// 词法分析核心逻辑
// -----------------------------------------------------------------------------
// 跳过空白字符(空格、制表符、换行符等)和注释
static void skip_whitespace_and_comments() {
while (current_char_g != EOF) {
if (is_whitespace(current_char_g)) {
// 如果是空白字符,直接跳过
advance();
} else if (current_char_g == '/') {
// 可能是注释或除法运算符
if (peek() == '/') {
// 单行注释 //
// 跳过当前字符 '/'
advance();
// 跳过下一个字符 '/'
advance();
// 一直跳过直到行尾或文件结束
while (current_char_g != '\n' && current_char_g != EOF) {
advance();
}
// 跳过换行符(如果存在)
if (current_char_g == '\n') {
advance();
}
} else if (peek() == '*') {
// 多行注释 /* ... */
// 跳过当前字符 '/'
advance();
// 跳过下一个字符 '*'
advance();
// 循环直到找到 "*/" 或文件结束
while (!(current_char_g == '*' && peek() == '/') && current_char_g != EOF) {
advance();
}
// 如果找到了 "*/",跳过 '*' 和 '/'
if (current_char_g == '*' && peek() == '/') {
advance(); // 跳过 '*'
advance(); // 跳过 '/'
} else {
// 如果文件结束了还没找到 "*/",说明注释未闭合,这是个错误
fprintf(stderr, "Error: Unclosed multi-line comment starting at line %d, column %d.\n", current_line_g, current_column_g - 2);
// 尽管是错误,我们仍然尝试继续解析,但实际编译器可能会选择终止
// 这里为了演示,我们继续,但在实际生产中应该更严格
break; // 退出循环,让后续的getToken处理EOF或下一个字符
}
} else {
// 只是一个普通的除号 '/',不跳过,让主逻辑处理
break;
}
} else {
// 既不是空白字符也不是注释,退出循环
break;
}
}
}
// 扫描标识符或关键字
// 从当前字符开始,读取所有符合标识符命名规则的字符,直到遇到不符合规则的字符
// 然后判断这个字符串是关键字还是普通标识符
static Token *scan_identifier_or_keyword(int start_line, int start_column) {
char buffer[256]; // 临时缓冲区,用于存储标识符或关键字的字符串
int i = 0;
// 读取所有字母、数字或下划线
while (is_alphanumeric(current_char_g) && i < sizeof(buffer) - 1) {
buffer[i++] = current_char_g;
advance();
}
buffer[i] = '\0'; // 字符串结束符
// 查找是否是关键字
TokenType type = lookup_keyword(buffer);
return create_token(type, buffer, start_line, start_column);
}
// 扫描整数常量
// 从当前字符开始,读取所有数字字符,直到遇到非数字字符
static Token *scan_number(int start_line, int start_column) {
char buffer[256]; // 临时缓冲区,用于存储数字字符串
int i = 0;
// 读取所有数字
while (is_digit(current_char_g) && i < sizeof(buffer) - 1) {
buffer[i++] = current_char_g;
advance();
}
buffer[i] = '\0'; // 字符串结束符
return create_token(TOKEN_INT_LITERAL, buffer, start_line, start_column);
}
// 扫描字符串常量
// 从当前字符开始,读取双引号内的所有字符,直到遇到另一个双引号
static Token *scan_string(int start_line, int start_column) {
// 跳过开头的双引号
advance();
char buffer[1024]; // 临时缓冲区,用于存储字符串内容
int i = 0;
// 循环直到遇到闭合的双引号或文件结束
while (current_char_g != '"' && current_char_g != EOF && i < sizeof(buffer) - 1) {
if (current_char_g == '\\') { // 处理转义字符
buffer[i++] = current_char_g; // 存储反斜杠
advance(); // 跳过反斜杠
if (current_char_g == EOF) {
fprintf(stderr, "Error: Unexpected EOF in string literal after backslash at line %d, column %d.\n", start_line, start_column);
return create_token(TOKEN_UNKNOWN, NULL, start_line, start_column);
}
}
buffer[i++] = current_char_g;
advance();
}
buffer[i] = '\0'; // 字符串结束符
if (current_char_g != '"') {
// 字符串未闭合,这是个错误
fprintf(stderr, "Error: Unclosed string literal starting at line %d, column %d.\n", start_line, start_column);
return create_token(TOKEN_UNKNOWN, NULL, start_line, start_column);
}
// 跳过闭合的双引号
advance();
return create_token(TOKEN_STRING_LITERAL, buffer, start_line, start_column);
}
// -----------------------------------------------------------------------------
// 词法分析器接口函数
// -----------------------------------------------------------------------------
// 词法分析器初始化
// 从文件中读取源代码到内存中
bool lexer_init(const char *filePath) {
FILE *file = fopen(filePath, "rb"); // 使用二进制模式读取,避免文本模式的换行符转换问题
if (!file) {
fprintf(stderr, "Error: Could not open source file '%s'\n", filePath);
return false;
}
// 获取文件大小
fseek(file, 0, SEEK_END);
long file_size = ftell(file);
fseek(file, 0, SEEK_SET);
// 分配内存来存储整个文件内容,额外加1字节用于存储字符串结束符'\0'
char *buffer = (char *)malloc(file_size + 1);
if (!buffer) {
fprintf(stderr, "Error: Failed to allocate memory for source code.\n");
fclose(file);
return false;
}
// 读取文件内容到缓冲区
size_t bytes_read = fread(buffer, 1, file_size, file);
buffer[bytes_read] = '\0'; // 添加字符串结束符
fclose(file);
source_code_g = buffer; // 将全局源代码指针指向缓冲区
current_pos_g = 0;
current_line_g = 1;
current_column_g = 1;
// 预读第一个字符
advance();
fprintf(stdout, "Lexer initialized successfully. Source file: '%s', size: %ld bytes.\n", filePath, file_size);
return true;
}
// 获取下一个Token的核心函数
Token *lexer_getToken() {
// 循环直到找到一个有效的Token或者到达文件末尾
while (current_char_g != EOF) {
// 1. 跳过空白字符和注释
skip_whitespace_and_comments();
// 记录当前Token的起始位置,用于错误报告和Token创建
int start_line = current_line_g;
int start_column = current_column_g;
// 如果跳过空白和注释后到达文件末尾,则返回EOF Token
if (current_char_g == EOF) {
return create_token(TOKEN_EOF, "EOF", start_line, start_column);
}
// 2. 识别标识符或关键字
if (is_alpha(current_char_g)) {
return scan_identifier_or_keyword(start_line, start_column);
}
// 3. 识别数字常量
if (is_digit(current_char_g)) {
return scan_number(start_line, start_column);
}
// 4. 识别字符串常量
if (current_char_g == '"') {
return scan_string(start_line, start_column);
}
// 5. 识别运算符和分隔符
switch (current_char_g) {
case '+':
advance();
if (current_char_g == '+') { advance(); return create_token(TOKEN_INC, "++", start_line, start_column); }
if (current_char_g == '=') { advance(); return create_token(TOKEN_PLUS_ASSIGN, "+=", start_line, start_column); }
return create_token(TOKEN_PLUS, "+", start_line, start_column);
case '-':
advance();
if (current_char_g == '-') { advance(); return create_token(TOKEN_DEC, "--", start_line, start_column); }
if (current_char_g == '=') { advance(); return create_token(TOKEN_MINUS_ASSIGN, "-=", start_line, start_column); }
if (current_char_g == '>') { advance(); return create_token(TOKEN_ARROW, "->", start_line, start_column); }
return create_token(TOKEN_MINUS, "-", start_line, start_column);
case '*':
advance();
if (current_char_g == '=') { advance(); return create_token(TOKEN_STAR_ASSIGN, "*=", start_line, start_column); }
return create_token(TOKEN_STAR, "*", start_line, start_column);
case '/': // 除号或注释已在 skip_whitespace_and_comments 中处理
advance();
if (current_char_g == '=') { advance(); return create_token(TOKEN_SLASH_ASSIGN, "/=", start_line, start_column); }
return create_token(TOKEN_SLASH, "/", start_line, start_column);
case '%':
advance();
if (current_char_g == '=') { advance(); return create_token(TOKEN_MODULO_ASSIGN, "%=", start_line, start_column); }
return create_token(TOKEN_MODULO, "%", start_line, start_column);
case '=':
advance();
if (current_char_g == '=') { advance(); return create_token(TOKEN_EQ, "==", start_line, start_column); }
return create_token(TOKEN_ASSIGN, "=", start_line, start_column);
case '!':
advance();
if (current_char_g == '=') { advance(); return create_token(TOKEN_NE, "!=", start_line, start_column); }
return create_token(TOKEN_EXCLAMATION, "!", start_line, start_column);
case '<':
advance();
if (current_char_g == '=') { advance(); return create_token(TOKEN_LE, "<=", start_line, start_column); }
if (current_char_g == '<') {
advance();
if (current_char_g == '=') { advance(); return create_token(TOKEN_LSHIFT_ASSIGN, "<<=", start_line, start_column); }
return create_token(TOKEN_LSHIFT, "<<", start_line, start_column);
}
return create_token(TOKEN_LT, "<", start_line, start_column);
case '>':
advance();
if (current_char_g == '=') { advance(); return create_token(TOKEN_GE, ">=", start_line, start_column); }
if (current_char_g == '>') {
advance();
if (current_char_g == '=') { advance(); return create_token(TOKEN_RSHIFT_ASSIGN, ">>=", start_line, start_column); }
return create_token(TOKEN_RSHIFT, ">>", start_line, start_column);
}
return create_token(TOKEN_GT, ">", start_line, start_column);
case '&':
advance();
if (current_char_g == '&') { advance(); return create_token(TOKEN_AND, "&&", start_line, start_column); }
if (current_char_g == '=') { advance(); return create_token(TOKEN_AMPERSAND_ASSIGN, "&=", start_line, start_column); }
return create_token(TOKEN_AMPERSAND, "&", start_line, start_column);
case '|':
advance();
if (current_char_g == '|') { advance(); return create_token(TOKEN_OR, "||", start_line, start_column); }
if (current_char_g == '=') { advance(); return create_token(TOKEN_PIPE_ASSIGN, "|=", start_line, start_column); }
return create_token(TOKEN_PIPE, "|", start_line, start_column);
case '^':
advance();
if (current_char_g == '=') { advance(); return create_token(TOKEN_CARET_ASSIGN, "^=", start_line, start_column); }
return create_token(TOKEN_CARET, "^", start_line, start_column);
case '~':
advance(); return create_token(TOKEN_TILDE, "~", start_line, start_column);
case '(':
advance(); return create_token(TOKEN_LPAREN, "(", start_line, start_column);
case ')':
advance(); return create_token(TOKEN_RPAREN, ")", start_line, start_column);
case '{':
advance(); return create_token(TOKEN_LBRACE, "{", start_line, start_column);
case '}':
advance(); return create_token(TOKEN_RBRACE, "}", start_line, start_column);
case '[':
advance(); return create_token(TOKEN_LBRACKET, "[", start_line, start_column);
case ']':
advance(); return create_token(TOKEN_RBRACKET, "]", start_line, start_column);
case ';':
advance(); return create_token(TOKEN_SEMICOLON, ";", start_line, start_column);
case ',':
advance(); return create_token(TOKEN_COMMA, ",", start_line, start_column);
case '.':
// 简单的点运算符,不处理浮点数或变长参数列表的省略号
advance(); return create_token(TOKEN_DOT, ".", start_line, start_column);
case ':':
advance(); return create_token(TOKEN_COLON, ":", start_line, start_column);
case '?':
advance(); return create_token(TOKEN_QUESTION, "?", start_line, start_column);
default:
// 遇到无法识别的字符,报告错误并返回UNKNOWN Token
fprintf(stderr, "Error: Unexpected character '%c' at line %d, column %d.\n", current_char_g, start_line, start_column);
char unknown_char_str[2] = {current_char_g, '\0'}; // 将未知字符转为字符串
advance(); // 跳过未知字符,继续扫描
return create_token(TOKEN_UNKNOWN, unknown_char_str, start_line, start_column);
}
}
// 如果循环结束,说明已经到达文件末尾
return create_token(TOKEN_EOF, "EOF", current_line_g, current_column_g);
}
// 词法分析器清理函数
void lexer_cleanup() {
if (source_code_g) {
free((void *)source_code_g); // 释放源代码缓冲区内存
source_code_g = NULL;
}
fprintf(stdout, "Lexer cleaned up.\n");
}
main.c
:测试我们的词法分析器
为了验证我们的词法分析器是否工作正常,我们需要一个 main
函数来调用它,并打印出识别到的Token。
#include "lexer.h"
#include <stdio.h>
#include <stdlib.h>
// 辅助函数:将TokenType转换为可读的字符串
// 用于打印Token信息时,更直观地显示Token类型
const char *token_type_to_string(TokenType type) {
switch (type) {
case TOKEN_EOF: return "EOF";
case TOKEN_UNKNOWN: return "UNKNOWN";
case TOKEN_KEYWORD_INT: return "KEYWORD_INT";
case TOKEN_KEYWORD_RETURN: return "KEYWORD_RETURN";
case TOKEN_KEYWORD_IF: return "KEYWORD_IF";
case TOKEN_KEYWORD_ELSE: return "KEYWORD_ELSE";
case TOKEN_KEYWORD_WHILE: return "KEYWORD_WHILE";
case TOKEN_KEYWORD_FOR: return "KEYWORD_FOR";
case TOKEN_KEYWORD_VOID: return "KEYWORD_VOID";
case TOKEN_KEYWORD_CHAR: return "KEYWORD_CHAR";
case TOKEN_KEYWORD_SHORT: return "KEYWORD_SHORT";
case TOKEN_KEYWORD_LONG: return "KEYWORD_LONG";
case TOKEN_KEYWORD_FLOAT: return "KEYWORD_FLOAT";
case TOKEN_KEYWORD_DOUBLE: return "KEYWORD_DOUBLE";
case TOKEN_KEYWORD_STRUCT: return "KEYWORD_STRUCT";
case TOKEN_KEYWORD_UNION: return "KEYWORD_UNION";
case TOKEN_KEYWORD_ENUM: return "KEYWORD_ENUM";
case TOKEN_KEYWORD_TYPEDEF: return "KEYWORD_TYPEDEF";
case TOKEN_KEYWORD_SIZEOF: return "KEYWORD_SIZEOF";
case TOKEN_KEYWORD_BREAK: return "KEYWORD_BREAK";
case TOKEN_KEYWORD_CONTINUE: return "KEYWORD_CONTINUE";
case TOKEN_KEYWORD_SWITCH: return "KEYWORD_SWITCH";
case TOKEN_KEYWORD_CASE: return "KEYWORD_CASE";
case TOKEN_KEYWORD_DEFAULT: return "KEYWORD_DEFAULT";
case TOKEN_KEYWORD_DO: return "KEYWORD_DO";
case TOKEN_KEYWORD_GOTO: return "KEYWORD_GOTO";
case TOKEN_KEYWORD_CONST: return "KEYWORD_CONST";
case TOKEN_KEYWORD_VOLATILE: return "KEYWORD_VOLATILE";
case TOKEN_KEYWORD_EXTERN: return "KEYWORD_EXTERN";
case TOKEN_KEYWORD_STATIC: return "KEYWORD_STATIC";
case TOKEN_KEYWORD_AUTO: return "KEYWORD_AUTO";
case TOKEN_KEYWORD_REGISTER: return "KEYWORD_REGISTER";
case TOKEN_KEYWORD_SIGNED: return "KEYWORD_SIGNED";
case TOKEN_KEYWORD_UNSIGNED: return "KEYWORD_UNSIGNED";
case TOKEN_IDENTIFIER: return "IDENTIFIER";
case TOKEN_INT_LITERAL: return "INT_LITERAL";
case TOKEN_STRING_LITERAL: return "STRING_LITERAL";
case TOKEN_PLUS: return "PLUS";
case TOKEN_MINUS: return "MINUS";
case TOKEN_STAR: return "STAR";
case TOKEN_SLASH: return "SLASH";
case TOKEN_MODULO: return "MODULO";
case TOKEN_ASSIGN: return "ASSIGN";
case TOKEN_LT: return "LT";
case TOKEN_GT: return "GT";
case TOKEN_AMPERSAND: return "AMPERSAND";
case TOKEN_PIPE: return "PIPE";
case TOKEN_CARET: return "CARET";
case TOKEN_TILDE: return "TILDE";
case TOKEN_EXCLAMATION: return "EXCLAMATION";
case TOKEN_DOT: return "DOT";
case TOKEN_COMMA: return "COMMA";
case TOKEN_COLON: return "COLON";
case TOKEN_QUESTION: return "QUESTION";
case TOKEN_EQ: return "EQ";
case TOKEN_NE: return "NE";
case TOKEN_LE: return "LE";
case TOKEN_GE: return "GE";
case TOKEN_AND: return "AND";
case TOKEN_OR: return "OR";
case TOKEN_INC: return "INC";
case TOKEN_DEC: return "DEC";
case TOKEN_PLUS_ASSIGN: return "PLUS_ASSIGN";
case TOKEN_MINUS_ASSIGN: return "MINUS_ASSIGN";
case TOKEN_STAR_ASSIGN: return "STAR_ASSIGN";
case TOKEN_SLASH_ASSIGN: return "SLASH_ASSIGN";
case TOKEN_MODULO_ASSIGN: return "MODULO_ASSIGN";
case TOKEN_LSHIFT: return "LSHIFT";
case TOKEN_RSHIFT: return "RSHIFT";
case TOKEN_LSHIFT_ASSIGN: return "LSHIFT_ASSIGN";
case TOKEN_RSHIFT_ASSIGN: return "RSHIFT_ASSIGN";
case TOKEN_AMPERSAND_ASSIGN: return "AMPERSAND_ASSIGN";
case TOKEN_PIPE_ASSIGN: return "PIPE_ASSIGN";
case TOKEN_CARET_ASSIGN: return "CARET_ASSIGN";
case TOKEN_ARROW: return "ARROW";
case TOKEN_LPAREN: return "LPAREN";
case TOKEN_RPAREN: return "RPAREN";
case TOKEN_LBRACE: return "LBRACE";
case TOKEN_RBRACE: return "RBRACE";
case TOKEN_LBRACKET: return "LBRACKET";
case TOKEN_RBRACKET: return "RBRACKET";
case TOKEN_SEMICOLON: return "SEMICOLON";
default: return "UNKNOWN_TYPE";
}
}
int main(int argc, char *argv[]) {
// 检查命令行参数,确保用户提供了源代码文件路径
if (argc != 2) {
fprintf(stderr, "Usage: %s <source_file.c>\n", argv[0]);
return EXIT_FAILURE;
}
const char *source_file_path = argv[1];
// 初始化词法分析器
if (!lexer_init(source_file_path)) {
fprintf(stderr, "Failed to initialize lexer for file: %s\n", source_file_path);
return EXIT_FAILURE;
}
Token *token;
// 循环获取并打印所有Token,直到遇到文件结束符EOF
do {
token = lexer_getToken(); // 获取下一个Token
// 打印Token信息:类型、值、所在行号、列号
printf("Token: Type = %-20s, Value = \"%s\", Line = %d, Column = %d\n",
token_type_to_string(token->type),
token->value ? token->value : "(null)", // 如果value是NULL,打印"(null)"
token->line,
token->column);
// 如果Token类型不是EOF,则释放当前Token的内存,准备获取下一个
if (token->type != TOKEN_EOF) {
free_token(token);
}
} while (token->type != TOKEN_EOF); // 当遇到EOF Token时,循环结束
// 释放最后一个EOF Token的内存
free_token(token);
// 清理词法分析器(释放源代码缓冲区内存)
lexer_cleanup();
printf("\nLexical analysis completed.\n");
return EXIT_SUCCESS;
}
示例C语言源代码文件:test.c
为了测试,咱们创建一个 test.c
文件:
// test.c
/* This is a multi-line comment.
* It spans multiple lines.
*/
int main() { // This is a single-line comment
int a = 10;
/* Another
* comment */
if (a > 5 && a != 100) {
return a + 1; // Return value
} else {
while (a <= 5) {
a++; // Increment a
}
}
char *str = "Hello, C compiler! \\n"; // String literal with escape sequence
printf("Value of a: %d\n", a); // A common function call
return 0;
}
编译和运行
-
保存文件:将上述三个代码块分别保存为
lexer.h
,lexer.c
,main.c
和test.c
。 -
编译:在终端中使用GCC编译(确保你安装了GCC):
gcc -o mylexer main.c lexer.c -Wall -Wextra -std=c99
-
-o mylexer
: 指定输出的可执行文件名为mylexer
。 -
main.c lexer.c
: 要编译的源文件。 -
-Wall -Wextra
: 开启所有常见和额外的警告,帮助我们发现潜在问题。 -
-std=c99
: 使用C99标准编译,确保stdbool.h
等特性可用。
-
-
运行:
./mylexer test.c
你将看到你的词法分析器将 test.c
中的代码分解成了一串串Token!
原理分析与逻辑剖析:词法分析器是如何“思考”的?
咱们的词法分析器,虽然简陋,但它已经包含了现代词法分析器的核心思想。
1. 字符流的线性扫描
词法分析器的工作方式是从左到右,一个字符一个字符地扫描源代码。advance()
函数是这个过程的核心,它负责读取下一个字符并更新当前位置(行号和列号),这是实现错误报告的关键。peek()
函数则允许我们“预读”一个字符,这对于识别多字符的Token(如 ==
, /*
)至关重要。
2. “跳过”无关紧要的字符
在C语言中,空格、制表符、换行符以及注释,它们的存在只是为了让代码更易读,对程序的实际执行逻辑没有影响。因此,词法分析器在识别Token之前,会先调用 skip_whitespace_and_comments()
来跳过这些“噪音”。
-
空白字符:
is_whitespace()
函数判断,直接advance()
跳过。 -
单行注释
//
:当遇到/
且下一个字符是/
时,就知道这是单行注释,然后一直advance()
直到遇到换行符或文件结束。 -
多行注释
/* ... */
:当遇到/
且下一个字符是*
时,就知道是多行注释。然后就进入一个循环,不断advance()
,直到找到*
和/
的组合。这里有一个重要的错误处理点:如果文件结束了还没找到*/
,说明注释未闭合,这是语法错误,需要报告。
3. 模式匹配与最长匹配原则
词法分析器的核心就是模式匹配。它会根据当前字符,尝试匹配最长的可能Token。
-
标识符与关键字:
-
当
current_char_g
是字母或下划线时,词法分析器知道它可能是一个标识符或关键字。 -
它会不断读取后续的字母、数字或下划线,直到遇到一个不符合规则的字符。
-
例如,如果源代码是
int main
:-
读到
i
,进入“标识符/关键字”模式。 -
读到
n
,继续。 -
读到
t
,继续。 -
读到空格,停止。此时,它得到了字符串
int
。
-
-
然后,它会拿着这个完整的字符串
int
去关键字表keywords
中查找。如果找到了,就识别为TOKEN_KEYWORD_INT
;如果没找到,就识别为TOKEN_IDENTIFIER
。 -
最长匹配原则:例如,如果有
int_var
这样的变量名,它会完整识别为int_var
,而不是先识别int
再识别_var
。
-
-
数字常量:
-
当
current_char_g
是数字时,词法分析器会不断读取后续的数字,直到遇到非数字字符。 -
例如,
123a
,它会识别出123
是一个TOKEN_INT_LITERAL
,然后a
会在下一次lexer_getToken()
调用时被处理。
-
-
字符串常量:
-
当遇到双引号
"
时,词法分析器知道这是一个字符串常量。 -
它会一直读取字符,直到遇到下一个双引号。
-
转义字符:如果遇到反斜杠
\
,它会特殊处理,比如\n
会被识别为换行符,但在这里我们只是简单地将\
和后面的字符一起存储到Token值中,具体的转义处理通常在语义分析或代码生成阶段进行。 -
未闭合的字符串:如果直到文件结束都没有找到闭合的双引号,这是一个错误,需要报告。
-
-
运算符和分隔符:
-
这些通常是单字符或双字符的。
-
对于单字符的,直接识别并返回。
-
对于双字符的,需要使用
peek()
来“偷看”下一个字符。例如,遇到=
,如果下一个字符也是=
,那就是==
;否则就是=
。这种贪心匹配的策略,确保了==
不会被错误地识别为两个=
。
-
4. Token的创建与内存管理
每次成功识别一个Token,create_token()
函数就会被调用,它会动态分配内存来存储 Token
结构体和它的字符串值(词素)。由于是动态分配的,所以调用者(main
函数)必须负责在Token使用完毕后调用 free_token()
来释放内存,避免内存泄漏。
5. 错误处理
虽然我们的词法分析器很简单,但它已经包含了基本的错误处理:
-
未闭合的注释:多行注释
/* ...
如果没有*/
闭合,会报告错误。 -
未闭合的字符串:字符串
"
如果没有"
闭合,会报告错误。 -
未知字符:如果遇到任何无法识别的字符(既不是空白、注释,也不是已知Token的开头),会报告“Unexpected character”错误。
这些错误报告对于后续的编译阶段(特别是语法分析)非常重要,因为词法错误会导致后续阶段无法正确解析。
词法分析器的工作流程(思维导图概念)
(这里想象一个流程图,描述 lexer_getToken()
的内部逻辑:
开始 -> 循环 (直到文件结束) -> 跳过空白和注释 -> 如果文件结束,返回 EOF Token -> 获取当前Token的起始行/列 -> 如果当前字符是字母或下划线? -> 是 -> 扫描标识符/关键字 -> 返回 Token -> 否 -> 如果当前字符是数字? -> 是 -> 扫描数字常量 -> 返回 Token -> 否 -> 如果当前字符是 "
? -> 是 -> 扫描字符串常量 -> 返回 Token -> 否 -> 根据当前字符,使用 switch
判断: Case '+': 检查下一个字符是否为 '+' 或 '=',返回 ++
, +=
或 +
Case '-': 检查下一个字符是否为 '-', '=', '>',返回 --
, -=
, ->
或 -
... (其他运算符和分隔符的类似逻辑) ... Default: 报告未知字符错误,返回 UNKNOWN
Token 循环结束 -> 返回 EOF Token )
这个流程图清晰地展示了 lexer_getToken()
函数的决策过程,它如何根据当前字符的类型,选择不同的扫描逻辑,并最终返回一个Token。
总结与展望:你已经迈出了编译器的第一步!
恭喜你,老铁!你已经成功迈出了“手撸C语言编译器”的第一步,亲手实现了一个简易但功能完备的词法分析器!
你现在应该明白:
-
词法分析器就像编译器的“眼睛”,它把源代码这堆“乱码”分解成一个个有意义的“单词”(Token)。
-
它通过线性扫描、模式匹配(有限状态机思想)和最长匹配原则来工作。
-
它会无情地抛弃空白字符和注释,只保留有用的信息。
虽然这只是编译器的第一步,但它却是构建后续所有复杂功能的基础。没有它,语法分析器就无法理解代码的结构,语义分析器就无法检查代码的含义,更别提生成机器码了!
你现在已经彻底掀开了C语言编译器的第一层“底裤”,是不是感觉C语言的“红尘”也没那么神秘了?
下一篇文章,我们将进入编译器的第二阶段——语法分析!我们将学习如何把这些零散的Token,按照C语言的语法规则,组织成一棵有层次、有结构的“语法树”。那才是真正让编译器“理解”C语言代码结构的关键!
敬请期待!如果你觉得这篇文章对你有帮助,请务必点赞、收藏、转发,让更多想搞懂C语言底层原理的兄弟们看到!你的支持是我继续创作的最大动力!
咱们下期不见不散!
------------------------------------------------------------------------------------更新于2025.6.2 晚8:19
彻底掀开C语言的“底裤”:手撸编译器系列(二)—— 语法分析的乾坤大挪移!
引言:从“单词”到“句子”——编译器的“阅读理解”能力!
兄弟们,上回咱们聊了词法分析,把C代码从一串字符流变成了有意义的“词法单元”(Token)流。就像你学英语,先得认识 apple
、eat
、I
这些单词。
但光认识单词没用啊!你得能把它们组成有意义的句子,比如 I eat apple.
,而不是 apple I eat.
或者 eat I apple.
。
编译器的“阅读理解”能力,就体现在它的第二个核心阶段——语法分析(Syntax Analysis),也叫解析(Parsing)。
词法分析器吐出来的Token流,就像一堆散落在地上的积木块。语法分析器的任务,就是根据C语言的“积木搭建规则”(语法规则),把这些积木块严丝合缝地拼起来,形成一个完整的、有逻辑的“积木结构”——抽象语法树(Abstract Syntax Tree, AST)。
你可能会问:
-
为什么需要语法分析?词法分析不是已经把代码拆解了吗?
-
什么是抽象语法树?它长啥样?有什么用?
-
语法分析器是怎么知道哪些Token能组成合法“句子”的?
今天,咱们就来彻底搞懂这些问题,并且,咱们还要亲手“手撸”一个简易的C语言语法分析器,让它把咱们的C代码变成一棵“看得见、摸得着”的语法树!
语法分析:编译器的“大脑”——构建代码的“骨架”!
语法分析器的核心任务,就是根据编程语言的语法规则(Grammar Rules),验证输入的Token流是否合法,并将其组织成一个层次化的结构。这个结构,就是AST。
为什么需要语法分析?
词法分析只关心“单词”本身是否合法,比如 int
是一个关键字,main
是一个标识符,123
是一个整数。但它不关心这些“单词”组合起来是否符合C语言的“文法”。
例如,以下两行代码:
-
int a = 10;
-
int = 10 a;
对于词法分析器来说,它们都能分解出 int
, =
, 10
, a
, ;
这些Token。但很明显,第二行代码是语法错误的!它不符合C语言的声明语句规则。
这就是语法分析器要干的活儿:检查Token序列是否符合语言的语法规范。 如果不符合,就报告语法错误。如果符合,就构建AST。
抽象语法树(Abstract Syntax Tree, AST):代码的“骨架”
AST 是源代码的抽象语法结构的树状表示。树中的每个节点都表示源代码中的一个构造。
(这里想象一个简单的AST图: C代码:int a = 1 + 2;
AST结构: FunctionDeclaration (main) -> VariableDeclaration (type: int, name: a) -> AssignmentExpression (=) -> BinaryExpression (+) -> IntegerLiteral (1) -> IntegerLiteral (2) )
AST的特点:
-
抽象:它移除了语法细节,比如括号、分号等,只保留了程序的核心结构和语义信息。例如,
a = 1 + 2;
和a = (1 + 2);
在AST中可能表示为相同的结构,因为它们的语义相同。 -
层次化:它清晰地表示了代码的嵌套关系和操作符的优先级。
-
通用:AST是一种中间表示,它独立于具体的源代码语言,也独立于目标机器。这使得编译器的后续阶段(语义分析、代码优化、代码生成)可以基于AST进行操作,而不需要直接处理原始源代码。
AST是编译器的“骨架”,后续的语义分析会在这棵骨架上填充“血肉”(类型信息、符号表等),代码生成器则会根据这棵骨架“打印”出机器指令。
语法分析器的工作原理:递归下降解析(Recursive Descent Parsing)
实现语法分析器有很多种方法,比如LL解析、LR解析等。对于咱们手撸一个简易编译器来说,**递归下降解析(Recursive Descent Parsing)**是最直观、最容易理解和实现的一种方法。
递归下降解析的核心思想
它的核心思想是:为语法规则中的每一个非终结符(Non-terminal,比如 函数声明
、语句
、表达式
)编写一个对应的函数。
这些函数会:
-
匹配(Match) 预期的Token。
-
递归调用 其他函数来解析子结构。
-
构建 AST节点。
(这里想象一个流程图: parse_program()
-> parse_function_declaration()
-> parse_type_specifier()
parse_identifier()
match(TOKEN_LPAREN)
match(TOKEN_RPAREN)
parse_compound_statement()
-> match(TOKEN_LBRACE)
parse_statement()
(loop) -> parse_return_statement()
-> match(TOKEN_KEYWORD_RETURN)
parse_expression()
-> parse_term()
-> parse_factor()
-> match(TOKEN_INT_LITERAL)
match(TOKEN_SEMICOLON)
match(TOKEN_RBRACE)
)
语法规则(EBNF表示)
为了让我们的语法分析器有章可循,咱们先定义一个简化的C语言子集的语法规则,用**扩展巴科斯范式(Extended Backus-Naur Form, EBNF)**来表示。
-
program ::= function_declaration*
-
一个程序由零个或多个函数声明组成。
-
-
function_declaration ::= type_specifier identifier '(' ')' compound_statement
-
函数声明由类型说明符、标识符、一对括号和复合语句组成。
-
-
type_specifier ::= 'int' | 'void'
-
类型说明符目前只支持
int
和void
。
-
-
compound_statement ::= '{' statement* '}'
-
复合语句(代码块)由一对大括号和零个或多个语句组成。
-
-
statement ::= declaration_statement | expression_statement | return_statement | if_statement | while_statement | compound_statement
-
一个语句可以是声明语句、表达式语句、返回语句、if语句、while语句或复合语句。
-
-
declaration_statement ::= type_specifier identifier ('=' expression)? ';'
-
声明语句由类型说明符、标识符、可选的赋值表达式和分号组成。
-
-
expression_statement ::= (expression)? ';'
-
表达式语句由可选的表达式和分号组成(例如
a++;
或;
空语句)。
-
-
return_statement ::= 'return' expression? ';'
-
返回语句由
return
关键字、可选的表达式和分号组成。
-
-
if_statement ::= 'if' '(' expression ')' statement ('else' statement)?
-
if 语句由
if
关键字、括号内的表达式、一个语句和可选的else
子句组成。
-
-
while_statement ::= 'while' '(' expression ')' statement
-
while 语句由
while
关键字、括号内的表达式和一个语句组成。
-
-
expression ::= assignment_expression
-
表达式可以是一个赋值表达式。
-
-
assignment_expression ::= logical_or_expression (('=' | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '>>=') assignment_expression)?
-
赋值表达式支持各种赋值运算符。
-
-
logical_or_expression ::= logical_and_expression ('||' logical_or_expression)?
-
逻辑或表达式。
-
-
logical_and_expression ::= equality_expression ('&&' logical_and_expression)?
-
逻辑与表达式。
-
-
equality_expression ::= relational_expression (('==' | '!=') equality_expression)?
-
相等性表达式。
-
-
relational_expression ::= shift_expression (('<' | '>' | '<=' | '>=') relational_expression)?
-
关系表达式。
-
-
shift_expression ::= additive_expression (('<<' | '>>') shift_expression)?
-
移位表达式。
-
-
additive_expression ::= multiplicative_expression (('+' | '-') additive_expression)?
-
加减法表达式。
-
-
multiplicative_expression ::= unary_expression (('*' | '/' | '%') multiplicative_expression)?
-
乘除模表达式。
-
-
unary_expression ::= ('+' | '-' | '!' | '~' | '++' | '--' | '&' | '*')? primary_expression
-
一元表达式,支持正负号、逻辑非、位非、自增、自减、取地址、解引用。
-
-
primary_expression ::= INT_LITERAL | STRING_LITERAL | IDENTIFIER | '(' expression ')'
-
基本表达式,可以是整数常量、字符串常量、标识符或括号括起来的表达式。
-
这个语法规则看起来有点复杂,但别怕!它遵循了运算符的优先级和结合性。在递归下降解析中,优先级高的运算符在更深层的函数中解析,结合性则通过递归调用来实现。
抽象语法树(AST)的数据结构定义
为了构建AST,我们需要定义一系列C语言的结构体来表示不同类型的AST节点。
#ifndef AST_H
#define AST_H
#include <stdlib.h> // For malloc, free
#include <stdio.h> // For printf, fprintf
#include <string.h> // For strdup
// AST节点类型枚举
typedef enum {
AST_PROGRAM, // 整个程序
AST_FUNCTION_DECLARATION, // 函数声明
AST_COMPOUND_STATEMENT, // 复合语句(代码块)
AST_VARIABLE_DECLARATION, // 变量声明
AST_RETURN_STATEMENT, // return 语句
AST_IF_STATEMENT, // if 语句
AST_WHILE_STATEMENT, // while 语句
AST_EXPRESSION_STATEMENT, // 表达式语句 (e.g., a = 10; a++;)
AST_BINARY_EXPRESSION, // 二元表达式 (e.g., a + b, x == y)
AST_UNARY_EXPRESSION, // 一元表达式 (e.g., -a, !b, ++c)
AST_ASSIGNMENT_EXPRESSION, // 赋值表达式 (e.g., a = b, x += y)
AST_IDENTIFIER, // 标识符 (变量名, 函数名)
AST_INT_LITERAL, // 整数常量
AST_STRING_LITERAL, // 字符串常量
// AST_CALL_EXPRESSION, // 函数调用表达式 (后续扩展)
// AST_TYPE_SPECIFIER, // 类型说明符 (int, void等,作为节点属性而非独立节点)
} ASTNodeType;
// 表达式操作符类型枚举 (用于二元和一元表达式)
typedef enum {
OP_NONE,
// 二元运算符
OP_ADD, // +
OP_SUB, // -
OP_MUL, // *
OP_DIV, // /
OP_MOD, // %
OP_EQ, // ==
OP_NE, // !=
OP_LT, // <
OP_GT, // >
OP_LE, // <=
OP_GE, // >=
OP_AND, // &&
OP_OR, // ||
OP_BIT_AND, // &
OP_BIT_OR, // |
OP_BIT_XOR, // ^
OP_LSHIFT, // <<
OP_RSHIFT, // >>
// 赋值运算符
OP_ASSIGN, // =
OP_ADD_ASSIGN, // +=
OP_SUB_ASSIGN, // -=
OP_MUL_ASSIGN, // *=
OP_DIV_ASSIGN, // /=
OP_MOD_ASSIGN, // %=
OP_LSHIFT_ASSIGN, // <<=
OP_RSHIFT_ASSIGN, // >>=
OP_BIT_AND_ASSIGN,// &=
OP_BIT_OR_ASSIGN, // |=
OP_BIT_XOR_ASSIGN,// ^=
// 一元运算符
OP_NEGATE, // - (负号)
OP_NOT, // ! (逻辑非)
OP_BIT_NOT, // ~ (位非)
OP_INCREMENT, // ++ (自增)
OP_DECREMENT, // -- (自减)
OP_ADDRESS_OF,// & (取地址)
OP_DEREFERENCE// * (解引用)
} OperatorType;
// 前向声明所有AST节点类型,以便它们可以相互引用
struct ASTNode;
struct ASTProgram;
struct ASTFunctionDeclaration;
struct ASTCompoundStatement;
struct ASTVariableDeclaration;
struct ASTReturnStatement;
struct ASTIfStatement;
struct ASTWhileStatement;
struct ASTExpressionStatement;
struct ASTBinaryExpression;
struct ASTUnaryExpression;
struct ASTAssignmentExpression;
struct ASTIdentifier;
struct ASTIntLiteral;
struct ASTStringLiteral;
// AST节点基类(所有具体AST节点的通用部分)
// 这是一个联合体,可以存储不同类型的AST节点数据
typedef struct ASTNode {
ASTNodeType type; // 节点的类型
// 联合体,根据type字段存储不同的节点数据
union {
struct ASTProgram *program;
struct ASTFunctionDeclaration *func_decl;
struct ASTCompoundStatement *compound_stmt;
struct ASTVariableDeclaration *var_decl;
struct ASTReturnStatement *return_stmt;
struct ASTIfStatement *if_stmt;
struct ASTWhileStatement *while_stmt;
struct ASTExpressionStatement *expr_stmt;
struct ASTBinaryExpression *binary_expr;
struct ASTUnaryExpression *unary_expr;
struct ASTAssignmentExpression *assign_expr;
struct ASTIdentifier *identifier;
struct ASTIntLiteral *int_literal;
struct ASTStringLiteral *string_literal;
} data;
} ASTNode;
// ASTProgram: 整个程序的根节点
typedef struct ASTProgram {
ASTNode **function_declarations; // 函数声明列表
int num_function_declarations; // 函数声明数量
int capacity_function_declarations; // 列表容量
} ASTProgram;
// ASTFunctionDeclaration: 函数声明节点
typedef struct ASTFunctionDeclaration {
char *return_type; // 返回类型 (e.g., "int", "void")
char *name; // 函数名
// 参数列表 (暂时不处理,后续扩展)
ASTNode *body; // 函数体 (通常是一个复合语句AST_COMPOUND_STATEMENT)
} ASTFunctionDeclaration;
// ASTCompoundStatement: 复合语句节点 (代码块)
typedef struct ASTCompoundStatement {
ASTNode **statements; // 语句列表
int num_statements; // 语句数量
int capacity_statements; // 列表容量
} ASTCompoundStatement;
// ASTVariableDeclaration: 变量声明节点
typedef struct ASTVariableDeclaration {
char *type; // 变量类型 (e.g., "int")
char *name; // 变量名
ASTNode *initializer; // 初始化表达式 (可选,如果声明时有赋值)
} ASTVariableDeclaration;
// ASTReturnStatement: return 语句节点
typedef struct ASTReturnStatement {
ASTNode *expression; // return 的表达式 (可选)
} ASTReturnStatement;
// ASTIfStatement: if 语句节点
typedef struct ASTIfStatement {
ASTNode *condition; // 条件表达式
ASTNode *then_statement; // if 为真时执行的语句
ASTNode *else_statement; // else 分支语句 (可选)
} ASTIfStatement;
// ASTWhileStatement: while 语句节点
typedef struct ASTWhileStatement {
ASTNode *condition; // 循环条件表达式
ASTNode *body; // 循环体语句
} ASTWhileStatement;
// ASTExpressionStatement: 表达式语句节点 (如 `a = 10;` 或 `func();`)
typedef struct ASTExpressionStatement {
ASTNode *expression; // 表达式
} ASTExpressionStatement;
// ASTBinaryExpression: 二元表达式节点 (a + b, x == y)
typedef struct ASTBinaryExpression {
OperatorType op; // 运算符类型
ASTNode *left; // 左操作数
ASTNode *right; // 右操作数
} ASTBinaryExpression;
// ASTUnaryExpression: 一元表达式节点 (-a, !b)
typedef struct ASTUnaryExpression {
OperatorType op; // 运算符类型
ASTNode *operand; // 操作数
} ASTUnaryExpression;
// ASTAssignmentExpression: 赋值表达式节点 (a = b, x += y)
typedef struct ASTAssignmentExpression {
OperatorType op; // 赋值运算符类型 (e.g., OP_ASSIGN, OP_ADD_ASSIGN)
ASTNode *left; // 左操作数 (通常是标识符)
ASTNode *right; // 右操作数 (赋值的值)
} ASTAssignmentExpression;
// ASTIdentifier: 标识符节点
typedef struct ASTIdentifier {
char *name; // 标识符的名称
} ASTIdentifier;
// ASTIntLiteral: 整数常量节点
typedef struct ASTIntLiteral {
int value; // 整数值
} ASTIntLiteral;
// ASTStringLiteral: 字符串常量节点
typedef struct ASTStringLiteral {
char *value; // 字符串值
} ASTStringLiteral;
// -----------------------------------------------------------------------------
// AST节点创建辅助函数
// -----------------------------------------------------------------------------
// 创建一个通用的ASTNode节点,并初始化其类型
ASTNode *create_ast_node(ASTNodeType type);
// 创建具体的AST节点并填充数据
ASTNode *create_ast_program();
ASTNode *create_ast_function_declaration(const char *return_type, const char *name, ASTNode *body);
ASTNode *create_ast_compound_statement();
ASTNode *create_ast_variable_declaration(const char *type, const char *name, ASTNode *initializer);
ASTNode *create_ast_return_statement(ASTNode *expression);
ASTNode *create_ast_if_statement(ASTNode *condition, ASTNode *then_stmt, ASTNode *else_stmt);
ASTNode *create_ast_while_statement(ASTNode *condition, ASTNode *body);
ASTNode *create_ast_expression_statement(ASTNode *expression);
ASTNode *create_ast_binary_expression(OperatorType op, ASTNode *left, ASTNode *right);
ASTNode *create_ast_unary_expression(OperatorType op, ASTNode *operand);
ASTNode *create_ast_assignment_expression(OperatorType op, ASTNode *left, ASTNode *right);
ASTNode *create_ast_identifier(const char *name);
ASTNode *create_ast_int_literal(int value);
ASTNode *create_ast_string_literal(const char *value);
// 向ASTProgram中添加函数声明
void ast_program_add_function_declaration(ASTProgram *program, ASTNode *func_decl_node);
// 向ASTCompoundStatement中添加语句
void ast_compound_statement_add_statement(ASTCompoundStatement *compound_stmt, ASTNode *statement_node);
// -----------------------------------------------------------------------------
// AST节点内存释放函数
// -----------------------------------------------------------------------------
// 递归释放AST节点的内存
void free_ast_node(ASTNode *node);
#endif // AST_H
ast.c
:AST数据结构的实现
#include "ast.h"
#include <stdio.h> // For fprintf
// -----------------------------------------------------------------------------
// AST节点创建辅助函数的实现
// -----------------------------------------------------------------------------
// 创建一个通用的ASTNode节点,并初始化其类型
ASTNode *create_ast_node(ASTNodeType type) {
ASTNode *node = (ASTNode *)malloc(sizeof(ASTNode));
if (!node) {
fprintf(stderr, "Error: Failed to allocate memory for ASTNode.\n");
exit(EXIT_FAILURE);
}
node->type = type;
// 初始化联合体内容为NULL或0,避免野指针
memset(&node->data, 0, sizeof(node->data));
return node;
}
// 创建ASTProgram节点
ASTNode *create_ast_program() {
ASTNode *node = create_ast_node(AST_PROGRAM);
node->data.program = (ASTProgram *)malloc(sizeof(ASTProgram));
if (!node->data.program) {
fprintf(stderr, "Error: Failed to allocate memory for ASTProgram.\n");
exit(EXIT_FAILURE);
}
node->data.program->function_declarations = NULL;
node->data.program->num_function_declarations = 0;
node->data.program->capacity_function_declarations = 0;
return node;
}
// 创建ASTFunctionDeclaration节点
ASTNode *create_ast_function_declaration(const char *return_type, const char *name, ASTNode *body) {
ASTNode *node = create_ast_node(AST_FUNCTION_DECLARATION);
node->data.func_decl = (ASTFunctionDeclaration *)malloc(sizeof(ASTFunctionDeclaration));
if (!node->data.func_decl) {
fprintf(stderr, "Error: Failed to allocate memory for ASTFunctionDeclaration.\n");
exit(EXIT_FAILURE);
}
node->data.func_decl->return_type = strdup(return_type);
node->data.func_decl->name = strdup(name);
node->data.func_decl->body = body;
return node;
}
// 创建ASTCompoundStatement节点
ASTNode *create_ast_compound_statement() {
ASTNode *node = create_ast_node(AST_COMPOUND_STATEMENT);
node->data.compound_stmt = (ASTCompoundStatement *)malloc(sizeof(ASTCompoundStatement));
if (!node->data.compound_stmt) {
fprintf(stderr, "Error: Failed to allocate memory for ASTCompoundStatement.\n");
exit(EXIT_FAILURE);
}
node->data.compound_stmt->statements = NULL;
node->data.compound_stmt->num_statements = 0;
node->data.compound_stmt->capacity_statements = 0;
return node;
}
// 创建ASTVariableDeclaration节点
ASTNode *create_ast_variable_declaration(const char *type, const char *name, ASTNode *initializer) {
ASTNode *node = create_ast_node(AST_VARIABLE_DECLARATION);
node->data.var_decl = (ASTVariableDeclaration *)malloc(sizeof(ASTVariableDeclaration));
if (!node->data.var_decl) {
fprintf(stderr, "Error: Failed to allocate memory for ASTVariableDeclaration.\n");
exit(EXIT_FAILURE);
}
node->data.var_decl->type = strdup(type);
node->data.var_decl->name = strdup(name);
node->data.var_decl->initializer = initializer;
return node;
}
// 创建ASTReturnStatement节点
ASTNode *create_ast_return_statement(ASTNode *expression) {
ASTNode *node = create_ast_node(AST_RETURN_STATEMENT);
node->data.return_stmt = (ASTReturnStatement *)malloc(sizeof(ASTReturnStatement));
if (!node->data.return_stmt) {
fprintf(stderr, "Error: Failed to allocate memory for ASTReturnStatement.\n");
exit(EXIT_FAILURE);
}
node->data.return_stmt->expression = expression;
return node;
}
// 创建ASTIfStatement节点
ASTNode *create_ast_if_statement(ASTNode *condition, ASTNode *then_stmt, ASTNode *else_stmt) {
ASTNode *node = create_ast_node(AST_IF_STATEMENT);
node->data.if_stmt = (ASTIfStatement *)malloc(sizeof(ASTIfStatement));
if (!node->data.if_stmt) {
fprintf(stderr, "Error: Failed to allocate memory for ASTIfStatement.\n");
exit(EXIT_FAILURE);
}
node->data.if_stmt->condition = condition;
node->data.if_stmt->then_statement = then_stmt;
node->data.if_stmt->else_statement = else_stmt;
return node;
}
// 创建ASTWhileStatement节点
ASTNode *create_ast_while_statement(ASTNode *condition, ASTNode *body) {
ASTNode *node = create_ast_node(AST_WHILE_STATEMENT);
node->data.while_stmt = (ASTWhileStatement *)malloc(sizeof(ASTWhileStatement));
if (!node->data.while_stmt) {
fprintf(stderr, "Error: Failed to allocate memory for ASTWhileStatement.\n");
exit(EXIT_FAILURE);
}
node->data.while_stmt->condition = condition;
node->data.while_stmt->body = body;
return node;
}
// 创建ASTExpressionStatement节点
ASTNode *create_ast_expression_statement(ASTNode *expression) {
ASTNode *node = create_ast_node(AST_EXPRESSION_STATEMENT);
node->data.expr_stmt = (ASTExpressionStatement *)malloc(sizeof(ASTExpressionStatement));
if (!node->data.expr_stmt) {
fprintf(stderr, "Error: Failed to allocate memory for ASTExpressionStatement.\n");
exit(EXIT_FAILURE);
}
node->data.expr_stmt->expression = expression;
return node;
}
// 创建ASTBinaryExpression节点
ASTNode *create_ast_binary_expression(OperatorType op, ASTNode *left, ASTNode *right) {
ASTNode *node = create_ast_node(AST_BINARY_EXPRESSION);
node->data.binary_expr = (ASTBinaryExpression *)malloc(sizeof(ASTBinaryExpression));
if (!node->data.binary_expr) {
fprintf(stderr, "Error: Failed to allocate memory for ASTBinaryExpression.\n");
exit(EXIT_FAILURE);
}
node->data.binary_expr->op = op;
node->data.binary_expr->left = left;
node->data.binary_expr->right = right;
return node;
}
// 创建ASTUnaryExpression节点
ASTNode *create_ast_unary_expression(OperatorType op, ASTNode *operand) {
ASTNode *node = create_ast_node(AST_UNARY_EXPRESSION);
node->data.unary_expr = (ASTUnaryExpression *)malloc(sizeof(ASTUnaryExpression));
if (!node->data.unary_expr) {
fprintf(stderr, "Error: Failed to allocate memory for ASTUnaryExpression.\n");
exit(EXIT_FAILURE);
}
node->data.unary_expr->op = op;
node->data.unary_expr->operand = operand;
return node;
}
// 创建ASTAssignmentExpression节点
ASTNode *create_ast_assignment_expression(OperatorType op, ASTNode *left, ASTNode *right) {
ASTNode *node = create_ast_node(AST_ASSIGNMENT_EXPRESSION);
node->data.assign_expr = (ASTAssignmentExpression *)malloc(sizeof(ASTAssignmentExpression));
if (!node->data.assign_expr) {
fprintf(stderr, "Error: Failed to allocate memory for ASTAssignmentExpression.\n");
exit(EXIT_FAILURE);
}
node->data.assign_expr->op = op;
node->data.assign_expr->left = left;
node->data.assign_expr->right = right;
return node;
}
// 创建ASTIdentifier节点
ASTNode *create_ast_identifier(const char *name) {
ASTNode *node = create_ast_node(AST_IDENTIFIER);
node->data.identifier = (ASTIdentifier *)malloc(sizeof(ASTIdentifier));
if (!node->data.identifier) {
fprintf(stderr, "Error: Failed to allocate memory for ASTIdentifier.\n");
exit(EXIT_FAILURE);
}
node->data.identifier->name = strdup(name);
return node;
}
// 创建ASTIntLiteral节点
ASTNode *create_ast_int_literal(int value) {
ASTNode *node = create_ast_node(AST_INT_LITERAL);
node->data.int_literal = (ASTIntLiteral *)malloc(sizeof(ASTIntLiteral));
if (!node->data.int_literal) {
fprintf(stderr, "Error: Failed to allocate memory for ASTIntLiteral.\n");
exit(EXIT_FAILURE);
}
node->data.int_literal->value = value;
return node;
}
// 创建ASTStringLiteral节点
ASTNode *create_ast_string_literal(const char *value) {
ASTNode *node = create_ast_node(AST_STRING_LITERAL);
node->data.string_literal = (ASTStringLiteral *)malloc(sizeof(ASTStringLiteral));
if (!node->data.string_literal) {
fprintf(stderr, "Error: Failed to allocate memory for ASTStringLiteral.\n");
exit(EXIT_FAILURE);
}
node->data.string_literal->value = strdup(value);
return node;
}
// 向ASTProgram中添加函数声明
void ast_program_add_function_declaration(ASTProgram *program, ASTNode *func_decl_node) {
if (program->num_function_declarations >= program->capacity_function_declarations) {
// 扩容
int new_capacity = program->capacity_function_declarations == 0 ? 4 : program->capacity_function_declarations * 2;
ASTNode **new_declarations = (ASTNode **)realloc(program->function_declarations, new_capacity * sizeof(ASTNode *));
if (!new_declarations) {
fprintf(stderr, "Error: Failed to reallocate memory for function declarations.\n");
exit(EXIT_FAILURE);
}
program->function_declarations = new_declarations;
program->capacity_function_declarations = new_capacity;
}
program->function_declarations[program->num_function_declarations++] = func_decl_node;
}
// 向ASTCompoundStatement中添加语句
void ast_compound_statement_add_statement(ASTCompoundStatement *compound_stmt, ASTNode *statement_node) {
if (compound_stmt->num_statements >= compound_stmt->capacity_statements) {
// 扩容
int new_capacity = compound_stmt->capacity_statements == 0 ? 8 : compound_stmt->capacity_statements * 2;
ASTNode **new_statements = (ASTNode **)realloc(compound_stmt->statements, new_capacity * sizeof(ASTNode *));
if (!new_statements) {
fprintf(stderr, "Error: Failed to reallocate memory for statements.\n");
exit(EXIT_FAILURE);
}
compound_stmt->statements = new_statements;
compound_stmt->capacity_statements = new_capacity;
}
compound_stmt->statements[compound_stmt->num_statements++] = statement_node;
}
// -----------------------------------------------------------------------------
// AST节点内存释放函数的实现
// -----------------------------------------------------------------------------
// 递归释放AST节点的内存
void free_ast_node(ASTNode *node) {
if (!node) {
return;
}
switch (node->type) {
case AST_PROGRAM:
if (node->data.program) {
for (int i = 0; i < node->data.program->num_function_declarations; ++i) {
free_ast_node(node->data.program->function_declarations[i]);
}
free(node->data.program->function_declarations);
free(node->data.program);
}
break;
case AST_FUNCTION_DECLARATION:
if (node->data.func_decl) {
free(node->data.func_decl->return_type);
free(node->data.func_decl->name);
free_ast_node(node->data.func_decl->body);
free(node->data.func_decl);
}
break;
case AST_COMPOUND_STATEMENT:
if (node->data.compound_stmt) {
for (int i = 0; i < node->data.compound_stmt->num_statements; ++i) {
free_ast_node(node->data.compound_stmt->statements[i]);
}
free(node->data.compound_stmt->statements);
free(node->data.compound_stmt);
}
break;
case AST_VARIABLE_DECLARATION:
if (node->data.var_decl) {
free(node->data.var_decl->type);
free(node->data.var_decl->name);
free_ast_node(node->data.var_decl->initializer);
free(node->data.var_decl);
}
break;
case AST_RETURN_STATEMENT:
if (node->data.return_stmt) {
free_ast_node(node->data.return_stmt->expression);
free(node->data.return_stmt);
}
break;
case AST_IF_STATEMENT:
if (node->data.if_stmt) {
free_ast_node(node->data.if_stmt->condition);
free_ast_node(node->data.if_stmt->then_statement);
free_ast_node(node->data.if_stmt->else_statement); // else_statement可能为NULL
free(node->data.if_stmt);
}
break;
case AST_WHILE_STATEMENT:
if (node->data.while_stmt) {
free_ast_node(node->data.while_stmt->condition);
free_ast_node(node->data.while_stmt->body);
free(node->data.while_stmt);
}
break;
case AST_EXPRESSION_STATEMENT:
if (node->data.expr_stmt) {
free_ast_node(node->data.expr_stmt->expression);
free(node->data.expr_stmt);
}
break;
case AST_BINARY_EXPRESSION:
if (node->data.binary_expr) {
free_ast_node(node->data.binary_expr->left);
free_ast_node(node->data.binary_expr->right);
free(node->data.binary_expr);
}
break;
case AST_UNARY_EXPRESSION:
if (node->data.unary_expr) {
free_ast_node(node->data.unary_expr->operand);
free(node->data.unary_expr);
}
break;
case AST_ASSIGNMENT_EXPRESSION:
if (node->data.assign_expr) {
free_ast_node(node->data.assign_expr->left);
free_ast_node(node->data.assign_expr->right);
free(node->data.assign_expr);
}
break;
case AST_IDENTIFIER:
if (node->data.identifier) {
free(node->data.identifier->name);
free(node->data.identifier);
}
break;
case AST_INT_LITERAL:
// 整数常量没有动态分配的字符串,直接释放结构体
if (node->data.int_literal) {
free(node->data.int_literal);
}
break;
case AST_STRING_LITERAL:
if (node->data.string_literal) {
free(node->data.string_literal->value);
free(node->data.string_literal);
}
break;
default:
// 未知类型,或者不需要特殊处理的类型
break;
}
free(node); // 最后释放ASTNode本身
}
语法分析器实现:parser.h
和 parser.c
现在,咱们开始编写语法分析器的核心逻辑。
parser.h
:定义解析器接口
#ifndef PARSER_H
#define PARSER_H
#include "lexer.h" // 包含词法分析器头文件,因为解析器需要Token
#include "ast.h" // 包含AST头文件,因为解析器需要构建AST
// 全局的当前Token指针
// parser_current_token_g: 当前正在处理的Token
extern Token *parser_current_token_g;
// 解析器初始化函数
// 参数:无
// 返回值:无
void parser_init();
// 解析整个程序
// 这是解析器的入口点,它会从Token流中构建整个程序的AST
// 返回值:指向ASTProgram根节点的指针
ASTNode *parse_program();
// 释放解析器资源(主要是当前Token)
void parser_cleanup();
// 辅助函数:将ASTNodeType转换为可读字符串(用于调试打印AST)
const char *ast_node_type_to_string(ASTNodeType type);
// 辅助函数:将OperatorType转换为可读字符串(用于调试打印AST)
const char *operator_type_to_string(OperatorType op);
// 辅助函数:打印AST(用于调试)
void print_ast(ASTNode *node, int indent);
#endif // PARSER_H
parser.c
:语法分析器的核心实现
#include "parser.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
// 全局的当前Token指针
Token *parser_current_token_g = NULL;
// -----------------------------------------------------------------------------
// 辅助函数:Token操作
// -----------------------------------------------------------------------------
// 获取下一个Token并更新全局当前Token
static void get_next_token() {
// 如果当前Token不是NULL,先释放它,避免内存泄漏
if (parser_current_token_g != NULL) {
free_token(parser_current_token_g);
}
// 从词法分析器获取下一个Token
parser_current_token_g = lexer_getToken();
}
// 检查当前Token的类型是否符合预期
// 参数:expected_type - 预期的Token类型
// 返回值:如果符合预期返回true,否则返回false
static bool check_token(TokenType expected_type) {
return parser_current_token_g->type == expected_type;
}
// 匹配并消耗一个指定类型的Token
// 如果当前Token类型与expected_type匹配,则消耗该Token并获取下一个Token;
// 否则,报告语法错误并退出。
// 参数:expected_type - 预期的Token类型
// 返回值:无
static void match(TokenType expected_type) {
if (!check_token(expected_type)) {
fprintf(stderr, "Syntax Error: Expected token type %s, but got %s ('%s') at line %d, column %d.\n",
token_type_to_string(expected_type),
token_type_to_string(parser_current_token_g->type),
parser_current_token_g->value ? parser_current_token_g->value : "",
parser_current_token_g->line,
parser_current_token_g->column);
exit(EXIT_FAILURE); // 遇到语法错误,直接退出
}
// 匹配成功,获取下一个Token
get_next_token();
}
// 报告语法错误并退出
static void parser_error(const char *message) {
fprintf(stderr, "Syntax Error: %s at line %d, column %d (Token: '%s', Type: %s).\n",
message,
parser_current_token_g->line,
parser_current_token_g->column,
parser_current_token_g->value ? parser_current_token_g->value : "",
token_type_to_string(parser_current_token_g->type));
exit(EXIT_FAILURE);
}
// -----------------------------------------------------------------------------
// 递归下降解析函数 (对应EBNF语法规则)
// -----------------------------------------------------------------------------
// 前向声明所有解析函数,因为它们会相互递归调用
static ASTNode *parse_type_specifier();
static ASTNode *parse_expression();
static ASTNode *parse_assignment_expression();
static ASTNode *parse_logical_or_expression();
static ASTNode *parse_logical_and_expression();
static ASTNode *parse_equality_expression();
static ASTNode *parse_relational_expression();
static ASTNode *parse_shift_expression();
static ASTNode *parse_additive_expression();
static ASTNode *parse_multiplicative_expression();
static ASTNode *parse_unary_expression();
static ASTNode *parse_primary_expression();
static ASTNode *parse_statement();
static ASTNode *parse_declaration_statement();
static ASTNode *parse_expression_statement();
static ASTNode *parse_return_statement();
static ASTNode *parse_if_statement();
static ASTNode *parse_while_statement();
static ASTNode *parse_compound_statement();
static ASTNode *parse_function_declaration();
// 解析类型说明符 (int, void)
// type_specifier ::= 'int' | 'void'
static ASTNode *parse_type_specifier() {
// 记录当前Token的词素,作为类型名
char *type_name = NULL;
if (check_token(TOKEN_KEYWORD_INT)) {
type_name = strdup(parser_current_token_g->value);
match(TOKEN_KEYWORD_INT);
} else if (check_token(TOKEN_KEYWORD_VOID)) {
type_name = strdup(parser_current_token_g->value);
match(TOKEN_KEYWORD_VOID);
} else {
parser_error("Expected a type specifier (int or void).");
}
// 这里我们不创建独立的AST_TYPE_SPECIFIER节点,直接返回类型字符串
// 并在需要的地方使用它,如AST_FUNCTION_DECLARATION和AST_VARIABLE_DECLARATION
// 这样做是为了简化AST结构,因为类型更多是属性而非独立操作
// 注意:这里返回的是一个字符串,而不是ASTNode。
// 在实际使用时,需要将其作为ASTNode的属性,并负责其内存管理。
// 为了与ASTNode*返回类型兼容,我们暂时返回一个ASTIdentifier节点来携带这个字符串
// 但更规范的做法是让调用者直接获取这个字符串,或者定义一个AST_TYPE节点。
// 这里为了简化演示,我们暂时用ASTIdentifier来“伪装”类型名,后续可优化。
// 更好的做法是在需要类型字符串的地方直接返回char*,并由调用者负责strdup和free。
// 为了保持ASTNode*的统一返回类型,我们创建一个临时的ASTIdentifier来承载类型名
// 实际编译器中,类型信息会更复杂,通常会通过符号表和类型系统来管理。
return create_ast_identifier(type_name); // 暂时用identifier节点来传递类型名
}
// 解析主表达式 (最高优先级,如字面量、标识符、括号表达式)
// primary_expression ::= INT_LITERAL | STRING_LITERAL | IDENTIFIER | '(' expression ')'
static ASTNode *parse_primary_expression() {
ASTNode *node = NULL;
if (check_token(TOKEN_INT_LITERAL)) {
// 将字符串值转换为整数
int value = atoi(parser_current_token_g->value);
node = create_ast_int_literal(value);
match(TOKEN_INT_LITERAL);
} else if (check_token(TOKEN_STRING_LITERAL)) {
node = create_ast_string_literal(parser_current_token_g->value);
match(TOKEN_STRING_LITERAL);
} else if (check_token(TOKEN_IDENTIFIER)) {
node = create_ast_identifier(parser_current_token_g->value);
match(TOKEN_IDENTIFIER);
} else if (check_token(TOKEN_LPAREN)) {
match(TOKEN_LPAREN);
node = parse_expression(); // 递归解析括号内的表达式
match(TOKEN_RPAREN);
} else {
parser_error("Expected an integer literal, string literal, identifier, or '(' for primary expression.");
}
return node;
}
// 解析一元表达式
// unary_expression ::= ('+' | '-' | '!' | '~' | '++' | '--' | '&' | '*')? primary_expression
static ASTNode *parse_unary_expression() {
OperatorType op_type = OP_NONE;
if (check_token(TOKEN_PLUS)) {
op_type = OP_ADD; // 正号
match(TOKEN_PLUS);
} else if (check_token(TOKEN_MINUS)) {
op_type = OP_NEGATE; // 负号
match(TOKEN_MINUS);
} else if (check_token(TOKEN_EXCLAMATION)) {
op_type = OP_NOT; // 逻辑非
match(TOKEN_EXCLAMATION);
} else if (check_token(TOKEN_TILDE)) {
op_type = OP_BIT_NOT; // 位非
match(TOKEN_TILDE);
} else if (check_token(TOKEN_INC)) {
op_type = OP_INCREMENT; // 前置自增
match(TOKEN_INC);
} else if (check_token(TOKEN_DEC)) {
op_type = OP_DECREMENT; // 前置自减
match(TOKEN_DEC);
} else if (check_token(TOKEN_AMPERSAND)) {
op_type = OP_ADDRESS_OF; // 取地址
match(TOKEN_AMPERSAND);
} else if (check_token(TOKEN_STAR)) {
op_type = OP_DEREFERENCE; // 解引用
match(TOKEN_STAR);
}
ASTNode *operand = parse_primary_expression(); // 解析操作数
if (op_type != OP_NONE) {
return create_ast_unary_expression(op_type, operand);
} else {
return operand; // 如果没有一元运算符,直接返回主表达式
}
}
// 解析乘除模表达式 (优先级高于加减)
// multiplicative_expression ::= unary_expression (('*' | '/' | '%') multiplicative_expression)?
static ASTNode *parse_multiplicative_expression() {
ASTNode *node = parse_unary_expression(); // 先解析一元表达式
while (check_token(TOKEN_STAR) || check_token(TOKEN_SLASH) || check_token(TOKEN_MODULO)) {
OperatorType op_type;
if (check_token(TOKEN_STAR)) {
op_type = OP_MUL;
match(TOKEN_STAR);
} else if (check_token(TOKEN_SLASH)) {
op_type = OP_DIV;
match(TOKEN_SLASH);
} else { // TOKEN_MODULO
op_type = OP_MOD;
match(TOKEN_MODULO);
}
// 递归解析右侧的乘除模表达式,实现左结合性
node = create_ast_binary_expression(op_type, node, parse_unary_expression()); // 注意这里是parse_unary_expression
}
return node;
}
// 解析加减法表达式 (优先级低于乘除)
// additive_expression ::= multiplicative_expression (('+' | '-') additive_expression)?
static ASTNode *parse_additive_expression() {
ASTNode *node = parse_multiplicative_expression(); // 先解析乘除模表达式
while (check_token(TOKEN_PLUS) || check_token(TOKEN_MINUS)) {
OperatorType op_type;
if (check_token(TOKEN_PLUS)) {
op_type = OP_ADD;
match(TOKEN_PLUS);
} else { // TOKEN_MINUS
op_type = OP_SUB;
match(TOKEN_MINUS);
}
// 递归解析右侧的加减法表达式,实现左结合性
node = create_ast_binary_expression(op_type, node, parse_multiplicative_expression()); // 注意这里是parse_multiplicative_expression
}
return node;
}
// 解析移位表达式
// shift_expression ::= additive_expression (('<<' | '>>') shift_expression)?
static ASTNode *parse_shift_expression() {
ASTNode *node = parse_additive_expression();
while (check_token(TOKEN_LSHIFT) || check_token(TOKEN_RSHIFT)) {
OperatorType op_type;
if (check_token(TOKEN_LSHIFT)) {
op_type = OP_LSHIFT;
match(TOKEN_LSHIFT);
} else { // TOKEN_RSHIFT
op_type = OP_RSHIFT;
match(TOKEN_RSHIFT);
}
node = create_ast_binary_expression(op_type, node, parse_additive_expression());
}
return node;
}
// 解析关系表达式 (<, >, <=, >=)
// relational_expression ::= shift_expression (('<' | '>' | '<=' | '>=') relational_expression)?
static ASTNode *parse_relational_expression() {
ASTNode *node = parse_shift_expression();
while (check_token(TOKEN_LT) || check_token(TOKEN_GT) || check_token(TOKEN_LE) || check_token(TOKEN_GE)) {
OperatorType op_type;
if (check_token(TOKEN_LT)) {
op_type = OP_LT;
match(TOKEN_LT);
} else if (check_token(TOKEN_GT)) {
op_type = OP_GT;
match(TOKEN_GT);
} else if (check_token(TOKEN_LE)) {
op_type = OP_LE;
match(TOKEN_LE);
} else { // TOKEN_GE
op_type = OP_GE;
match(TOKEN_GE);
}
node = create_ast_binary_expression(op_type, node, parse_shift_expression());
}
return node;
}
// 解析相等性表达式 (==, !=)
// equality_expression ::= relational_expression (('==' | '!=') equality_expression)?
static ASTNode *parse_equality_expression() {
ASTNode *node = parse_relational_expression();
while (check_token(TOKEN_EQ) || check_token(TOKEN_NE)) {
OperatorType op_type;
if (check_token(TOKEN_EQ)) {
op_type = OP_EQ;
match(TOKEN_EQ);
} else { // TOKEN_NE
op_type = OP_NE;
match(TOKEN_NE);
}
node = create_ast_binary_expression(op_type, node, parse_relational_expression());
}
return node;
}
// 解析逻辑与表达式 (&&)
// logical_and_expression ::= equality_expression ('&&' logical_and_expression)?
static ASTNode *parse_logical_and_expression() {
ASTNode *node = parse_equality_expression();
while (check_token(TOKEN_AND)) {
match(TOKEN_AND);
node = create_ast_binary_expression(OP_AND, node, parse_equality_expression());
}
return node;
}
// 解析逻辑或表达式 (||)
// logical_or_expression ::= logical_and_expression ('||' logical_or_expression)?
static ASTNode *parse_logical_or_expression() {
ASTNode *node = parse_logical_and_expression();
while (check_token(TOKEN_OR)) {
match(TOKEN_OR);
node = create_ast_binary_expression(OP_OR, node, parse_logical_and_expression());
}
return node;
}
// 解析赋值表达式 (优先级最低,右结合性)
// assignment_expression ::= logical_or_expression (('=' | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '>>=') assignment_expression)?
static ASTNode *parse_assignment_expression() {
ASTNode *left_expr = parse_logical_or_expression();
// 检查是否是赋值运算符
if (check_token(TOKEN_ASSIGN) || check_token(TOKEN_PLUS_ASSIGN) || check_token(TOKEN_MINUS_ASSIGN) ||
check_token(TOKEN_STAR_ASSIGN) || check_token(TOKEN_SLASH_ASSIGN) || check_token(TOKEN_MODULO_ASSIGN) ||
check_token(TOKEN_AMPERSAND_ASSIGN) || check_token(TOKEN_PIPE_ASSIGN) || check_token(TOKEN_CARET_ASSIGN) ||
check_token(TOKEN_LSHIFT_ASSIGN) || check_token(TOKEN_RSHIFT_ASSIGN)) {
OperatorType op_type;
TokenType current_token_type = parser_current_token_g->type;
if (current_token_type == TOKEN_ASSIGN) op_type = OP_ASSIGN;
else if (current_token_type == TOKEN_PLUS_ASSIGN) op_type = OP_ADD_ASSIGN;
else if (current_token_type == TOKEN_MINUS_ASSIGN) op_type = OP_SUB_ASSIGN;
else if (current_token_type == TOKEN_STAR_ASSIGN) op_type = OP_MUL_ASSIGN;
else if (current_token_type == TOKEN_SLASH_ASSIGN) op_type = OP_DIV_ASSIGN;
else if (current_token_type == TOKEN_MODULO_ASSIGN) op_type = OP_MOD_ASSIGN;
else if (current_token_type == TOKEN_AMPERSAND_ASSIGN) op_type = OP_BIT_AND_ASSIGN;
else if (current_token_type == TOKEN_PIPE_ASSIGN) op_type = OP_BIT_OR_ASSIGN;
else if (current_token_type == TOKEN_CARET_ASSIGN) op_type = OP_BIT_XOR_ASSIGN;
else if (current_token_type == TOKEN_LSHIFT_ASSIGN) op_type = OP_LSHIFT_ASSIGN;
else /* if (current_token_type == TOKEN_RSHIFT_ASSIGN) */ op_type = OP_RSHIFT_ASSIGN;
match(current_token_type); // 消耗赋值运算符
ASTNode *right_expr = parse_assignment_expression(); // 递归解析右侧的赋值表达式,实现右结合性
return create_ast_assignment_expression(op_type, left_expr, right_expr);
}
return left_expr; // 如果没有赋值运算符,则只是一个逻辑或表达式
}
// 解析表达式 (目前只处理赋值表达式,后续可扩展逗号表达式等)
// expression ::= assignment_expression
static ASTNode *parse_expression() {
return parse_assignment_expression();
}
// 解析声明语句
// declaration_statement ::= type_specifier identifier ('=' expression)? ';'
static ASTNode *parse_declaration_statement() {
// 获取类型说明符
ASTNode *type_node = parse_type_specifier(); // 暂时返回ASTIdentifier来承载类型名
char *type_name = strdup(type_node->data.identifier->name); // 复制类型名
free_ast_node(type_node); // 释放临时类型节点
// 获取标识符(变量名)
if (!check_token(TOKEN_IDENTIFIER)) {
parser_error("Expected an identifier after type specifier in declaration.");
}
char *var_name = strdup(parser_current_token_g->value);
match(TOKEN_IDENTIFIER);
ASTNode *initializer = NULL;
if (check_token(TOKEN_ASSIGN)) { // 如果有赋值号,说明有初始化表达式
match(TOKEN_ASSIGN);
initializer = parse_expression(); // 解析初始化表达式
}
match(TOKEN_SEMICOLON); // 匹配分号
return create_ast_variable_declaration(type_name, var_name, initializer);
}
// 解析表达式语句
// expression_statement ::= (expression)? ';'
static ASTNode *parse_expression_statement() {
ASTNode *expr = NULL;
if (!check_token(TOKEN_SEMICOLON)) { // 如果不是分号,说明有表达式
expr = parse_expression();
}
match(TOKEN_SEMICOLON); // 匹配分号
return create_ast_expression_statement(expr);
}
// 解析返回语句
// return_statement ::= 'return' expression? ';'
static ASTNode *parse_return_statement() {
match(TOKEN_KEYWORD_RETURN); // 匹配 return 关键字
ASTNode *expr = NULL;
if (!check_token(TOKEN_SEMICOLON)) { // 如果不是分号,说明有返回表达式
expr = parse_expression();
}
match(TOKEN_SEMICOLON); // 匹配分号
return create_ast_return_statement(expr);
}
// 解析 if 语句
// if_statement ::= 'if' '(' expression ')' statement ('else' statement)?
static ASTNode *parse_if_statement() {
match(TOKEN_KEYWORD_IF); // 匹配 if 关键字
match(TOKEN_LPAREN); // 匹配 '('
ASTNode *condition = parse_expression(); // 解析条件表达式
match(TOKEN_RPAREN); // 匹配 ')'
ASTNode *then_statement = parse_statement(); // 解析 if 为真时执行的语句
ASTNode *else_statement = NULL;
if (check_token(TOKEN_KEYWORD_ELSE)) { // 如果有 else 关键字
match(TOKEN_KEYWORD_ELSE);
else_statement = parse_statement(); // 解析 else 分支语句
}
return create_ast_if_statement(condition, then_statement, else_statement);
}
// 解析 while 语句
// while_statement ::= 'while' '(' expression ')' statement
static ASTNode *parse_while_statement() {
match(TOKEN_KEYWORD_WHILE); // 匹配 while 关键字
match(TOKEN_LPAREN); // 匹配 '('
ASTNode *condition = parse_expression(); // 解析循环条件
match(TOKEN_RPAREN); // 匹配 ')'
ASTNode *body = parse_statement(); // 解析循环体语句
return create_ast_while_statement(condition, body);
}
// 解析语句
// statement ::= declaration_statement | expression_statement | return_statement | if_statement | while_statement | compound_statement
static ASTNode *parse_statement() {
if (check_token(TOKEN_KEYWORD_INT) || check_token(TOKEN_KEYWORD_VOID)) { // 检查是否是类型关键字,判断为声明语句
return parse_declaration_statement();
} else if (check_token(TOKEN_KEYWORD_RETURN)) {
return parse_return_statement();
} else if (check_token(TOKEN_KEYWORD_IF)) {
return parse_if_statement();
} else if (check_token(TOKEN_KEYWORD_WHILE)) {
return parse_while_statement();
} else if (check_token(TOKEN_LBRACE)) { // 如果是 '{',说明是复合语句
return parse_compound_statement();
} else {
// 否则,尝试解析为表达式语句(包括空语句)
return parse_expression_statement();
}
}
// 解析复合语句 (代码块)
// compound_statement ::= '{' statement* '}'
static ASTNode *parse_compound_statement() {
match(TOKEN_LBRACE); // 匹配 '{'
ASTNode *compound_stmt_node = create_ast_compound_statement();
// 循环解析语句,直到遇到 '}' 或文件结束
while (!check_token(TOKEN_RBRACE) && !check_token(TOKEN_EOF)) {
ASTNode *stmt = parse_statement();
if (stmt) {
ast_compound_statement_add_statement(compound_stmt_node->data.compound_stmt, stmt);
} else {
// 如果解析语句失败,可能是语法错误,或者遇到了无法识别的Token
// 这里可以添加更复杂的错误恢复机制,但目前简单地退出
parser_error("Failed to parse statement within compound statement.");
}
}
match(TOKEN_RBRACE); // 匹配 '}'
return compound_stmt_node;
}
// 解析函数声明
// function_declaration ::= type_specifier identifier '(' ')' compound_statement
static ASTNode *parse_function_declaration() {
// 解析返回类型
ASTNode *return_type_node = parse_type_specifier();
char *return_type_name = strdup(return_type_node->data.identifier->name); // 复制类型名
free_ast_node(return_type_node); // 释放临时类型节点
// 解析函数名
if (!check_token(TOKEN_IDENTIFIER)) {
parser_error("Expected function name after return type.");
}
char *func_name = strdup(parser_current_token_g->value);
match(TOKEN_IDENTIFIER);
match(TOKEN_LPAREN); // 匹配 '('
// TODO: 这里可以添加参数解析逻辑
match(TOKEN_RPAREN); // 匹配 ')'
ASTNode *body = parse_compound_statement(); // 解析函数体(复合语句)
return create_ast_function_declaration(return_type_name, func_name, body);
}
// -----------------------------------------------------------------------------
// 解析器接口函数实现
// -----------------------------------------------------------------------------
// 解析器初始化
void parser_init() {
// 获取第一个Token,启动词法分析器
get_next_token();
fprintf(stdout, "Parser initialized. First token: %s ('%s').\n",
token_type_to_string(parser_current_token_g->type),
parser_current_token_g->value ? parser_current_token_g->value : "");
}
// 解析整个程序
ASTNode *parse_program() {
ASTNode *program_node = create_ast_program();
// 循环解析函数声明,直到文件结束
while (!check_token(TOKEN_EOF)) {
ASTNode *func_decl = parse_function_declaration();
if (func_decl) {
ast_program_add_function_declaration(program_node->data.program, func_decl);
} else {
parser_error("Failed to parse function declaration.");
}
}
return program_node;
}
// 释放解析器资源
void parser_cleanup() {
if (parser_current_token_g) {
free_token(parser_current_token_g);
parser_current_token_g = NULL;
}
fprintf(stdout, "Parser cleaned up.\n");
}
// -----------------------------------------------------------------------------
// AST打印辅助函数 (用于调试和可视化AST)
// -----------------------------------------------------------------------------
// 辅助函数:打印缩进
static void print_indent(int indent) {
for (int i = 0; i < indent; ++i) {
printf(" "); // 每个缩进级别使用两个空格
}
}
// 辅助函数:将ASTNodeType转换为可读字符串
const char *ast_node_type_to_string(ASTNodeType type) {
switch (type) {
case AST_PROGRAM: return "PROGRAM";
case AST_FUNCTION_DECLARATION: return "FUNCTION_DECLARATION";
case AST_COMPOUND_STATEMENT: return "COMPOUND_STATEMENT";
case AST_VARIABLE_DECLARATION: return "VARIABLE_DECLARATION";
case AST_RETURN_STATEMENT: return "RETURN_STATEMENT";
case AST_IF_STATEMENT: return "IF_STATEMENT";
case AST_WHILE_STATEMENT: return "WHILE_STATEMENT";
case AST_EXPRESSION_STATEMENT: return "EXPRESSION_STATEMENT";
case AST_BINARY_EXPRESSION: return "BINARY_EXPRESSION";
case AST_UNARY_EXPRESSION: return "UNARY_EXPRESSION";
case AST_ASSIGNMENT_EXPRESSION: return "ASSIGNMENT_EXPRESSION";
case AST_IDENTIFIER: return "IDENTIFIER";
case AST_INT_LITERAL: return "INT_LITERAL";
case AST_STRING_LITERAL: return "STRING_LITERAL";
default: return "UNKNOWN_AST_NODE_TYPE";
}
}
// 辅助函数:将OperatorType转换为可读字符串
const char *operator_type_to_string(OperatorType op) {
switch (op) {
case OP_NONE: return "NONE";
case OP_ADD: return "+";
case OP_SUB: return "-";
case OP_MUL: return "*";
case OP_DIV: return "/";
case OP_MOD: return "%";
case OP_EQ: return "==";
case OP_NE: return "!=";
case OP_LT: return "<";
case OP_GT: return ">";
case OP_LE: return "<=";
case OP_GE: return ">=";
case OP_AND: return "&&";
case OP_OR: return "||";
case OP_BIT_AND: return "&";
case OP_BIT_OR: return "|";
case OP_BIT_XOR: return "^";
case OP_LSHIFT: return "<<";
case OP_RSHIFT: return ">>";
case OP_ASSIGN: return "=";
case OP_ADD_ASSIGN: return "+=";
case OP_SUB_ASSIGN: return "-=";
case OP_MUL_ASSIGN: return "*=";
case OP_DIV_ASSIGN: return "/=";
case OP_MOD_ASSIGN: return "%=";
case OP_LSHIFT_ASSIGN: return "<<=";
case OP_RSHIFT_ASSIGN: return ">>=";
case OP_BIT_AND_ASSIGN: return "&=";
case OP_BIT_OR_ASSIGN: return "|=";
case OP_BIT_XOR_ASSIGN: return "^=";
case OP_NEGATE: return "-(unary)";
case OP_NOT: return "!(logical)";
case OP_BIT_NOT: return "~(bitwise)";
case OP_INCREMENT: return "++(unary)";
case OP_DECREMENT: return "--(unary)";
case OP_ADDRESS_OF: return "&(address_of)";
case OP_DEREFERENCE: return "*(dereference)";
default: return "UNKNOWN_OPERATOR";
}
}
// 递归打印AST
void print_ast(ASTNode *node, int indent) {
if (!node) {
return;
}
print_indent(indent);
printf("Node Type: %s", ast_node_type_to_string(node->type));
switch (node->type) {
case AST_PROGRAM:
printf("\n");
for (int i = 0; i < node->data.program->num_function_declarations; ++i) {
print_ast(node->data.program->function_declarations[i], indent + 1);
}
break;
case AST_FUNCTION_DECLARATION:
printf(" (Name: %s, Return Type: %s)\n",
node->data.func_decl->name,
node->data.func_decl->return_type);
print_indent(indent + 1); printf("Body:\n");
print_ast(node->data.func_decl->body, indent + 2);
break;
case AST_COMPOUND_STATEMENT:
printf(" (Statements: %d)\n", node->data.compound_stmt->num_statements);
for (int i = 0; i < node->data.compound_stmt->num_statements; ++i) {
print_ast(node->data.compound_stmt->statements[i], indent + 1);
}
break;
case AST_VARIABLE_DECLARATION:
printf(" (Type: %s, Name: %s)\n",
node->data.var_decl->type,
node->data.var_decl->name);
if (node->data.var_decl->initializer) {
print_indent(indent + 1); printf("Initializer:\n");
print_ast(node->data.var_decl->initializer, indent + 2);
}
break;
case AST_RETURN_STATEMENT:
printf("\n");
if (node->data.return_stmt->expression) {
print_indent(indent + 1); printf("Expression:\n");
print_ast(node->data.return_stmt->expression, indent + 2);
}
break;
case AST_IF_STATEMENT:
printf("\n");
print_indent(indent + 1); printf("Condition:\n");
print_ast(node->data.if_stmt->condition, indent + 2);
print_indent(indent + 1); printf("Then:\n");
print_ast(node->data.if_stmt->then_statement, indent + 2);
if (node->data.if_stmt->else_statement) {
print_indent(indent + 1); printf("Else:\n");
print_ast(node->data.if_stmt->else_statement, indent + 2);
}
break;
case AST_WHILE_STATEMENT:
printf("\n");
print_indent(indent + 1); printf("Condition:\n");
print_ast(node->data.while_stmt->condition, indent + 2);
print_indent(indent + 1); printf("Body:\n");
print_ast(node->data.while_stmt->body, indent + 2);
break;
case AST_EXPRESSION_STATEMENT:
printf("\n");
if (node->data.expr_stmt->expression) {
print_indent(indent + 1); printf("Expression:\n");
print_ast(node->data.expr_stmt->expression, indent + 2);
}
break;
case AST_BINARY_EXPRESSION:
printf(" (Op: %s)\n", operator_type_to_string(node->data.binary_expr->op));
print_indent(indent + 1); printf("Left:\n");
print_ast(node->data.binary_expr->left, indent + 2);
print_indent(indent + 1); printf("Right:\n");
print_ast(node->data.binary_expr->right, indent + 2);
break;
case AST_UNARY_EXPRESSION:
printf(" (Op: %s)\n", operator_type_to_string(node->data.unary_expr->op));
print_indent(indent + 1); printf("Operand:\n");
print_ast(node->data.unary_expr->operand, indent + 2);
break;
case AST_ASSIGNMENT_EXPRESSION:
printf(" (Op: %s)\n", operator_type_to_string(node->data.assign_expr->op));
print_indent(indent + 1); printf("Left (Target):\n");
print_ast(node->data.assign_expr->left, indent + 2);
print_indent(indent + 1); printf("Right (Value):\n");
print_ast(node->data.assign_expr->right, indent + 2);
break;
case AST_IDENTIFIER:
printf(" (Name: %s)\n", node->data.identifier->name);
break;
case AST_INT_LITERAL:
printf(" (Value: %d)\n", node->data.int_literal->value);
break;
case AST_STRING_LITERAL:
printf(" (Value: \"%s\")\n", node->data.string_literal->value);
break;
default:
printf(" (Unhandled Node Type)\n");
break;
}
}
main.c
:更新以集成语法分析器
现在,咱们把词法分析器和语法分析器整合起来,并打印出生成的AST。
#include "lexer.h"
#include "parser.h" // 包含语法分析器头文件
#include "ast.h" // 包含AST头文件
#include <stdio.h>
#include <stdlib.h>
// main 函数现在将负责初始化词法分析器和语法分析器,然后调用解析器来构建AST
int main(int argc, char *argv[]) {
// 检查命令行参数,确保用户提供了源代码文件路径
if (argc != 2) {
fprintf(stderr, "Usage: %s <source_file.c>\n", argv[0]);
return EXIT_FAILURE;
}
const char *source_file_path = argv[1];
// 1. 初始化词法分析器
if (!lexer_init(source_file_path)) {
fprintf(stderr, "Failed to initialize lexer for file: %s\n", source_file_path);
return EXIT_FAILURE;
}
// 2. 初始化语法分析器 (它会自动获取第一个Token)
parser_init();
// 3. 开始解析整个程序,构建AST
printf("\n--- Starting Syntax Analysis ---\n");
ASTNode *program_ast = parse_program();
printf("--- Syntax Analysis Completed ---\n");
// 4. 打印生成的AST (用于调试和验证)
printf("\n--- Generated Abstract Syntax Tree (AST) ---\n");
print_ast(program_ast, 0); // 从根节点开始打印,初始缩进为0
printf("--------------------------------------------\n");
// 5. 清理AST内存
printf("\n--- Cleaning up AST memory ---\n");
free_ast_node(program_ast);
printf("--- AST memory cleaned up ---\n");
// 6. 清理词法分析器资源
lexer_cleanup();
// 7. 清理解析器资源 (主要是释放最后一个Token)
parser_cleanup();
printf("\nCompilation phase (Lexical and Syntax Analysis) completed successfully.\n");
return EXIT_SUCCESS;
}
示例C语言源代码文件:test.c
(更新,以测试更多语法规则)
为了测试我们的语法分析器,咱们创建一个新的 test.c
文件,包含更多我们支持的语法结构:
// test.c
/* This is a multi-line comment.
* It spans multiple lines.
*/
int sum(int a, int b) { // This is a single-line comment
int result = a + b;
if (result > 10) {
result = result * 2;
} else {
result = result - 5;
}
while (result < 0) {
result++;
}
return result;
}
void main() {
int x = 10;
int y = 20;
int z;
z = x + y * 2; // Test operator precedence
if (z == 50 || x != 10) {
z += 100;
}
x++; // Increment operator
y--; // Decrement operator
int final_val = sum(x, y);
printf("Final value: %d\n", final_val);
return; // Void function return
}
注意:
-
我们当前的语法分析器还不支持函数参数,所以
int sum(int a, int b)
这样的函数声明会被简化处理,参数会被忽略。这是为了简化本阶段的实现,后续可以扩展。 -
printf
函数调用目前也被视为一个普通的表达式语句,其内部的参数解析和类型检查将在语义分析阶段处理。
编译和运行
-
保存文件:将上述代码块分别保存为
ast.h
,ast.c
,parser.h
,parser.c
,lexer.h
,lexer.c
和test.c
。确保lexer.h
和lexer.c
是上一部分提供的最新版本。 -
编译:在终端中使用GCC编译(确保你安装了GCC):
gcc -o mycompiler main.c lexer.c parser.c ast.c -Wall -Wextra -std=c99
-
-o mycompiler
: 指定输出的可执行文件名为mycompiler
。 -
main.c lexer.c parser.c ast.c
: 要编译的所有源文件。 -
-Wall -Wextra
: 开启所有常见和额外的警告。 -
-std=c99
: 使用C99标准编译。
-
-
运行:
./mycompiler test.c
你将看到程序输出词法分析的Token流,然后是语法分析器构建的抽象语法树的结构化打印!这棵树就是你的C代码在编译器内部的“骨架”!
原理分析与逻辑剖析:语法分析器是如何“理解”C代码的?
咱们的语法分析器,虽然是简易版,但它已经能解析C语言的核心结构了。它的“理解”过程,完全是基于我们定义的EBNF语法规则,通过递归下降的方式一步步进行的。
1. Token流驱动
语法分析器不像词法分析器那样直接操作字符流,它操作的是词法分析器生成的Token流。get_next_token()
函数是它的核心,每次调用都会从词法分析器那里获取下一个Token,并更新 parser_current_token_g
。
2. match()
函数:规则的“守门员”
match(expected_type)
函数是递归下降解析的基石。它扮演着“守门员”的角色:
-
检查:它会检查当前Token的类型是否符合预期的
expected_type
。 -
消耗:如果匹配成功,它会“消耗”掉当前Token(通过调用
get_next_token()
获取下一个Token),表示这个Token已经被成功解析。 -
错误报告:如果类型不匹配,它会立即报告语法错误,并终止程序(在实际编译器中,这里会有更复杂的错误恢复机制,但为了简化,我们直接退出)。
3. 递归下降:函数即规则
解析器的每个 parse_xxx()
函数都严格对应着EBNF语法规则中的一个非终结符。
-
parse_program()
:程序的入口,它会循环调用parse_function_declaration()
来解析程序中的所有函数。-
program ::= function_declaration*
-
-
parse_function_declaration()
:解析一个函数声明。它会依次调用其他解析函数来匹配type_specifier
、identifier
、'('
、')'
和compound_statement
。-
function_declaration ::= type_specifier identifier '(' ')' compound_statement
-
-
parse_compound_statement()
:解析一个代码块。它会匹配{
,然后循环调用parse_statement()
来解析块内的所有语句,直到匹配}
。-
compound_statement ::= '{' statement* '}'
-
-
parse_statement()
:这是一个分发函数,它会根据当前Token的类型,判断应该调用哪个具体的语句解析函数(如parse_declaration_statement()
、parse_return_statement()
、parse_if_statement()
等)。-
statement ::= declaration_statement | expression_statement | return_statement | if_statement | while_statement | compound_statement
-
4. 表达式解析:优先级和结合性
表达式的解析是递归下降中最巧妙的部分,它通过函数的嵌套调用来自然地处理运算符的优先级和结合性。
-
优先级:优先级越高的运算符,其对应的解析函数被调用的层级越深。例如,
parse_multiplicative_expression()
会调用parse_unary_expression()
,而parse_additive_expression()
会调用parse_multiplicative_expression()
。这意味着乘除模运算会在加减运算之前被解析。-
additive_expression ::= multiplicative_expression (('+' | '-') additive_expression)?
-
multiplicative_expression ::= unary_expression (('*' | '/' | '%') multiplicative_expression)?
-
unary_expression ::= ... primary_expression
-
primary_expression ::= ...
(最高优先级)
-
-
结合性:
-
左结合性(如
a + b + c
等价于(a + b) + c
):通过while
循环和左递归(当前函数调用比它优先级低或同级的函数)来实现。例如,在parse_additive_expression()
中,node = create_ast_binary_expression(op_type, node, parse_multiplicative_expression());
这一行,node
会不断地作为左操作数被更新,从而形成左结合的AST结构。 -
右结合性(如赋值运算符
a = b = c
等价于a = (b = c)
):通过右递归来实现。例如,在parse_assignment_expression()
中,right_expr = parse_assignment_expression();
这一行,右侧的表达式会再次调用自身,从而形成右结合的AST结构。
-
5. AST的构建
每个 parse_xxx()
函数在成功解析其对应的语法结构后,都会调用 create_ast_xxx()
函数来创建一个新的AST节点,并将其子节点连接起来。最终,所有的节点会形成一棵完整的AST。
-
例如,
parse_binary_expression()
会创建AST_BINARY_EXPRESSION
节点,并将其左右操作数(通过递归调用其他解析函数获得)作为子节点。
6. 错误处理与恢复 (简易版)
目前我们的错误处理非常简单粗暴:一旦遇到语法错误,就 exit(EXIT_FAILURE)
。在实际的编译器中,错误恢复是一个复杂的话题,通常会尝试跳过错误部分,继续解析,以便发现更多的错误,或者提供更友好的错误提示。但对于手撸编译器来说,简单退出是可接受的起点。
语法分析器工作流程(思维导图概念)
(这里想象一个流程图,描述 parse_program()
和其子函数的调用关系:
parse_program()
-> parse_function_declaration()
(循环) -> parse_type_specifier()
-> match(TOKEN_IDENTIFIER)
(函数名) -> match(TOKEN_LPAREN)
-> match(TOKEN_RPAREN)
-> parse_compound_statement()
-> match(TOKEN_LBRACE)
-> parse_statement()
(循环) -> (根据Token类型分发到) parse_declaration_statement()
parse_expression_statement()
parse_return_statement()
parse_if_statement()
parse_while_statement()
parse_compound_statement()
(递归) -> match(TOKEN_SEMICOLON)
(对于语句) -> match(TOKEN_RBRACE)
-> match(TOKEN_EOF)
表达式解析的层级结构(AST结构图概念): z = x + y * 2;
AST_ASSIGNMENT_EXPRESSION
(=) Left: AST_IDENTIFIER
(z) Right: AST_BINARY_EXPRESSION
(+) Left: AST_IDENTIFIER
(x) Right: AST_BINARY_EXPRESSION
(*) Left: AST_IDENTIFIER
(y) Right: AST_INT_LITERAL
(2) )
这个流程图和AST结构图,清晰地展示了语法分析器如何将扁平的Token流,通过递归下降的方式,构建成具有层次结构的AST,从而“理解”代码的逻辑关系。
总结与展望:你已经拥有了编译器的“大脑”!
恭喜你,老铁!你已经成功地给你的C语言编译器装上了“大脑”——语法分析器!
你现在应该明白:
-
语法分析器是编译器的“阅读理解”专家,它负责检查Token流是否符合C语言的语法规则。
-
它的输出是抽象语法树(AST),这是代码的“骨架”,移除了语法细节,只保留了核心语义结构。
-
我们通过递归下降解析,为每条语法规则编写一个函数,通过函数的相互调用和匹配Token来构建AST,并巧妙地处理了运算符的优先级和结合性。
从字符流到Token流,再到抽象语法树,你已经一步步深入了C语言编译器的核心。你现在不仅仅是会写C代码,更开始理解C代码在计算机内部是如何被“理解”和表示的。是不是感觉对C语言的掌控力又提升了一个档次?
下一篇文章,我们将进入编译器的第三阶段——语义分析!我们将在这棵“骨架”(AST)上填充“血肉”,检查代码的含义是否合法,比如变量有没有定义、类型是否匹配、函数调用是否正确等等。那才是真正让编译器“理解”C语言代码“意思”的关键!
敬请期待!如果你觉得这篇文章让你醍醐灌顶,请务必点赞、收藏、转发,让更多想彻底搞懂C语言的兄弟们看到!你的支持是我继续创作的最大动力!
咱们下期不见不散!