语法分析与 AST¶
语法分析在补什么¶
词法分析结束后,编译器拿到的是一串 Token,但这还不是“程序结构”。
例如同样是一串 Token,编译器还需要知道:
- 哪些 Token 组成一个函数定义
- 哪些 Token 组成一个
if - 运算符优先级怎么结合
- 一个表达式的左右边界在哪里
这就是语法分析的任务。
先看一个最小例子¶
还是用这段代码:
Lexer 给 Parser 的输入更像是一串这样的 Token:
Parser 的任务不是再扫描字符,而是把这串 Token 组织成层次结构。
最后在概念上会得到这样的树:
这就是 AST 最直观的意义:原来只是排成一行的 Token,现在终于长成了一棵有上下级关系的树。
什么是 AST¶
AST 是抽象语法树,可以把它理解成“程序的结构化骨架”。
例如,一个函数在 AST 里不再只是文本片段,而会有更明确的字段:
- 返回类型
- 函数名
- 参数列表
- 函数体
表达式也一样:
- 二元表达式会有运算符、左操作数、右操作数
if语句会有条件、then 分支、else 分支
说得再直白一点,AST 就是把“排成一条龙的东西”掰成“谁包着谁、谁挂着谁”的结构。
如果没有这棵树,后面的语义分析和 IRBuilder 都要自己重新判断:
- 这里是不是函数
return的值是什么a + b的左右两边分别是谁
不然的话,后面的每一层都得重新猜一次结构,大家轮流做同一份苦工。
这个项目的 Parser 是递归下降¶
当前实现使用手写递归下降 Parser。
它的好处不是“显得高级”,而是特别适合这种第一代项目:
- 语法规则和代码函数对应关系直接
- 错误定位清晰
- 新增语法时改动容易控制
你在代码里能看到很明显的分工:
parse_program_parse_top_level_decl_parse_statement_parse_if_parse_while_parse_for- 一整套表达式解析函数
这也是为什么递归下降很适合拿来读源码。你不用先补一堆生成器背景,光看函数名,基本就知道它在啃哪块语法。
为什么表达式解析要分层¶
表达式不是简单从左到右扫过去就行,因为有优先级问题。
例如:
这里应该先算 b * c,再算 a + (...)。
所以 Parser 会按优先级拆成几层,例如:
- 赋值
- 逻辑或
- 逻辑与
- 相等比较
- 大小比较
- 加减
- 乘除模
- 一元表达式
- 后缀表达式
- 基础表达式
这就是递归下降里很常见的“按优先级分层解析”。
你可以把这个过程想成一层层往下问:
- 这是不是赋值?
- 如果不是,是不是逻辑或?
- 如果不是,是不是逻辑与?
- ...
- 最后落到字面量、名字或括号表达式
所以“递归下降”别被名字吓到,它没那么玄,很多时候就是一层层往下问:“你到底是哪一种表达式?”
这个项目当前能解析哪些高层结构¶
从实现上看,当前支持的核心语法包括:
- 顶层全局变量声明
- 函数定义
- 代码块
if / elsewhileforreturn- 局部变量声明
- 表达式语句
表达式方面支持:
- 字面量
- 变量名
- 一元运算
- 二元运算
- 赋值
- 函数调用
- 数组下标
从源码角度看,这些能力最终都会在 parser.py 里落成不同 AST 节点,而不是停留在字符串层面。
为什么“只按名字调用函数”是个有意限制¶
当前实现里,函数调用要求被调用者是名字,而不是任意表达式。
这意味着:
- 支持
foo(1, 2) - 不支持函数指针形式的更复杂调用
这不是缺陷记录漏了,而是当前语法和语义边界故意压在更可控的范围内。
这类边界很重要,因为它说明 Parser 现在不是要一口吞掉完整 C,而是先把自己管得住的这块地盘整理明白。
声明器为什么单独处理¶
当前 Parser 会把类型说明和声明器拆开看。
例如这些:
int xint *pchar s[8]
虽然它们基础类型只有 int 或 char,但声明器部分还要表达:
- 指针层级
- 数组大小
- 最终变量名
所以解析类型时,不只是认关键字,还要继续解析声明器形态。
这也是 C 语法对初学者最容易造成困扰的地方之一:类型关键字只是开始,声明器才决定最终这个名字到底是普通变量、指针还是数组。
Parser 的错误处理意味着什么¶
当 Parser 报错时,它给出的往往不是“程序坏了”这种模糊信息,而是:
- 期望有
; - 期望有
) - 期望类型说明符
- 当前 Token 不符合该语法位置
这说明 Parser 是按明确语法规则推进的,不是在做模糊猜测。
换句话说,Parser 不是来猜你心思的,它是来严格认结构的。
AST 为什么是后面所有阶段的核心输入¶
从这一步开始,后面的阶段基本都不再直接依赖原始 Token 序列,而是依赖 AST。
原因很简单:
- 语义分析更适合在结构化树上做
- IR Builder 更适合从语句和表达式节点下降
- AST 也可以直接作为高级后端路径的输入
所以 AST 是当前项目最重要的中间结构之一。
如果你真的想读懂这个项目,AST 这关绕不过去。因为从这里开始,后面很多页面说的都不是文本了,而是在说“这棵树接下来怎么继续被处理”。
如何观察这一层结果¶
可以直接运行:
如果词法没问题但语法结构不对,这个阶段通常最能暴露问题。
看 AST 的时候,重点建议观察:
- 顶层声明有没有被正确区分
- 表达式结合顺序对不对
- 控制流语句的层次是否正确
- 局部变量声明和普通表达式语句有没有被混淆