进阶 #compiler#parser#grammar

语法分析与上下文无关文法

语法分析(Syntax Analysis / Parsing)是编译的第二阶段——把 Token 序列组织成语法树,检查程序的结构是否符合语言的语法规则

有了单词序列,怎么知道谁是主语谁是谓语?

词法分析给了你一串 Token:int a = 42 ;——但这只是一串单词,没有结构。语法分析要回答:这些单词按什么规则组合在一起?

🏫 类比:句子的语法分析 “我喜欢编程” → 主谓宾结构。你知道这个结构是因为中文的语法规则。

编程语言的语法规则就是用上下文无关文法(Context-Free Grammar, CFG) 来定义的。

上下文无关文法

文法(Grammar) 由一组产生式(Production) 组成,每条产生式描述如何拆分一个语法成分:

表达式 → 数字
表达式 → 表达式 "+" 表达式
表达式 → 表达式 "*" 表达式
语句 → "int" 标识符 "=" 表达式 ";"
程序 → 语句 程序 | ε

文法的组成部分

G = (N, T, P, S)

N = 非终结符(需要展开的符号):表达式、语句、程序
T = 终结符(Token):int, =, +, ;, 标识符, 数字
P = 产生式:N → (N ∪ T)*
S = 开始符号:程序的顶层结构

推导——从开始符号生成句子

程序
→ 语句 程序
→ "int" 标识符 "=" 表达式 ";" 程序
→ "int" 标识符 "=" 表达式 "+" 表达式 ";" 程序
→ "int" 标识符 "=" 数字 "+" 数字 ";" 程序
→ "int" a "=" 42 "+" 1 ";" (没有更多语句了)

语法树(Parse Tree)

        程序
       /    \
    语句    程序(空)
   / |  |  | \
int id = 表达式 ;
         /  |  \
      表达式 + 表达式
        |       |
       42       1

二义性文法

同一个句子可能有多种语法树——这就是二义性

表达式 → 表达式 '+' 表达式
表达式 → 表达式 '*' 表达式
表达式 → 数字

输入:1 + 2 * 3

两种可能的语法树:
     +                 *
    / \               / \
   1   *             +   3
      / \           / \
     2   3         1   2

    结果:9          结果:7

解决方案:

  1. 改写文法——引入优先级层次
  2. 结合性声明——规定左结合或右结合
// 改写后的无二义文法
表达式 → 表达式 '+'|
项 → 项 '*' 因子 | 因子
因子 → 数字 | '(' 表达式 ')'

// 1 + 2 * 3 只有一种推导:
// 表达式 → 表达式 + 项 → 项 + 项 → 因子 + 项
//        → 1 + 项 → 1 + 项 * 因子 → 1 + 2 * 3

语法分析的两种方式

方式方向做法适用文法
自顶向下从开始符号→句子预测推导路径LL 文法
自底向上从句子→开始符号归约为非终结符LR 文法
自顶向下:程序 → 语句 → int a = 表达式; → ...
自底向上:int → 类型 ... → 语句 → 程序

💡 两种方法的对比如下:自顶向下(LL)实现简单、错误定位好,但能处理的文法种类有限;自底向上(LR)能处理更多文法,但实现更复杂。

Yacc / Bison——语法分析器生成器

Unix 工具 Yacc(Yet Another Compiler Compiler) 从文法规则自动生成语法分析器:

%{
#include "ast.h"
%}

%token NUMBER IDENTIFIER
%left '+' '-'
%left '*' '/'

%%

expression: expression '+' expression
          | expression '*' expression
          | NUMBER
          | IDENTIFIER
          ;

%%

int main() {
    yyparse();
    return 0;
}

小结

概念要点
上下文无关文法用产生式描述语言结构
推导从开始符号逐步展开到句子
语法树推导过程的树形表示
二义性同一句子多棵语法树 → 需要改写文法
自顶向下 vs 自底向上两种不同的解析策略

为什么先学这个? 理解语法分析后,接下来深入两种具体实现:自顶向下分析(LL)自底向上分析(LR)。你也可以直接跳到抽象语法树(AST)