目录

编译原理笔记

词法分析是编译原理的第一个阶段,词法分析的任务是读入源程序的输入字符,生成一个个的单词,其主要的功能是为语法分析提供词法单元

graph LR

S1((源程序))
S2[词法分析器]
S3[语法分析器]
S4((符号表))
S5[输出之语义分析]

S1-->S2-->S3-->S5

S3-->S2

S2-->S4
S4-->S2

S3-->S4
S4-->S3
  • 对于给定的正则表达式 $\Sigma$={c1, c2, c3…cn}
  • 归纳定义:
    • 对于空串是正则表达式$\epsilon$是正则表达式
    • 对于任何$c\in\Sigma$,$c$是正则表达式
    • 如果M和N都是正则表达式,那么下面的也是正则表达式
      • 选择: M | N = {M, N}
      • 连接: MN = {mn| m $\in$ M, n, $\in$ M }
      • 闭包: M* = { $\epsilon$ , M, MM, MMMM….}

使用flex学习正则表达式

Flex由三部分组成

定义部分
%%
规则部分
%%
用户附加的C语言部分
%%
[+-]?[0-9]+ { /* Print integers */
  printf("%s\n", yytext);
}

\n { /* newline */
}

. { /* For others, do nothing */
}
%%

void main(){
    yylex();
}

int yywrap(){
    return 1;
}

编译指令

#!/bin/sh

# 生成c源程序
flex lex.l

# 执行程序编译
gcc lex.yy.c
graph LR

输入的字符串 --> 有限状态自动机
有限状态自动机 --> 判断结果["{Yes, No}"]

M = ($\Epsilon$, $S$, $q0$, $F$, $\delta$)

  • $\Epsilon$ 字母表
  • $S$ 状态集
  • $q0$ 初始状态
  • $F$ 终止状态
  • $\delta$ 转移函数
  • 下面什么样子的串可以接受
graph LR

状态1((0))
状态2((1))
状态3((2))

状态1--b-->状态1
状态1--a-->状态2

状态2--b-->状态2
状态2--a-->状态3

状态3--a,b-->状态3
  • 转移函数
    • ($q0$, a) –> $q1$, ($q0$, b) –> $q0$,
    • ($q1$, a) –> $q2$, ($q1$, b) –> $q1$,
    • ($q2$, a) –> $q2$, ($q2$, b) –> $q2$,
graph LR
RE[正则表达式]
NFA[非确定有限自动机]
DFA[确定有限状态自动机]
mDFA[最简有限状态自动机]
RE--Thompson-->NFA--子集构造-->DFA--Hopcroft-->mDFA
  • 基于RE的结构进行归纳
    • 对基本的RE进行直接构造
    • 对于复合的RE进行递归构造
  • 递归,容易实现
    • 代码实现较少

$a(b|c)*$

编译原理中的语法分析是编译过程中的关键阶段,其核心任务是根据词法分析生成的词法单元序列,验证输入是否符合源语言的语法规则,并构建语法结构(如抽象语法树)。以下是语法分析的总结:


  • 验证语法正确性:检查词法单元序列是否符合文法规则。
  • 构建语法结构:生成语法树(Parse Tree)或抽象语法树(Abstract Syntax Tree, AST)。
  • 错误处理:检测并报告语法错误(如括号不匹配、语句结构错误)。

  • 上下文无关文法(CFG, Context-Free Grammar):语法分析的理论基础。
    • 四元组:( G = (N, T, P, S) )
      • ( N ):非终结符集合(如表达式、语句)。
      • ( T ):终结符集合(如关键字、运算符)。
      • ( P ):产生式集合(如 ( E \rightarrow E + T ))。
      • ( S ):开始符号。
  • 二义性文法:需避免或通过优先级/结合性规则解决。

  • 递归下降分析:手工编写递归函数,适合简单文法。
  • LL(k) 分析
    • 从左向右扫描输入,最左推导(Leftmost Derivation)。
    • 需要构造预测分析表(基于 FIRSTFOLLOW 集)。
    • 限制:无法处理左递归文法。
    • LL(1):最常用,向前看 1 个符号。
  • LR 分析
    • 从左向右扫描输入,最右推导的逆(Rightmost Derivation)。
    • 核心是构造状态机和动作表(移进、归约、接受、报错)。
    • 类型:
      • LR(0):基础版本,但能力有限。
      • SLR(1):简化版,基于 FOLLOW 集解决冲突。
      • LR(1):更强大但复杂。
      • LALR(1):平衡能力与复杂度(如 Yacc/Bison 使用)。
  • 算符优先分析:适用于表达式解析,基于运算符优先级和结合性。

  • FIRST 和 FOLLOW 集合:用于 LL 分析表构造。
  • LR 自动机构造:基于闭包(Closure)和转移(Goto)操作。
  • 工具
    • ANTLR:生成 LL(*) 分析器。
    • Yacc/Bison:生成 LALR(1) 分析器。
    • 递归下降手写解析器:灵活但需处理左递归和冲突。

  • 恐慌模式:跳过输入直到同步符号(如分号、右括号)。
  • 短语级恢复:局部替换或插入符号。
  • 错误产生式:在文法中预定义错误处理规则。

  • 去除了语法树中冗余的中间节点(如推导步骤),保留程序逻辑结构。
  • 示例:表达式 3 + 5 * 2 的 AST 直接体现运算符优先级,而非具体推导过程。

  • 左递归消除:将直接/间接左递归转换为右递归(如 ( E \rightarrow E + T ) → ( E \rightarrow T E’ ))。
  • 冲突解决
    • 移进-归约冲突:通过优先级/结合性解决(如 if-else 的最近匹配)。
    • 归约-归约冲突:调整文法规则。

语法分析是编译器的核心环节,理解其原理和工具应用对设计高效、健壮的编译器至关重要。

  • 递归下降
  • 预测分析LL(1)

鉴于自定向下分析法存在回溯的问题,对于现代编译器设计是不可以接受的;由此提出了LL(1)分析文法

给出文法

0: S -> N V M
1: N -> s
2:   | t
3:   | g
4:   | w
5: V -> e
6:   | d

那么同时给出LL(1)分析表

N\Tstgwed
S0000XX
N1234XX
VXXXX56

那么在分析g d w语句的时候,可以得到如下的分析

解析算法

tokens[];  // all tokens
i = 0;
stack = [S]  // S 是开始符号
while(stack[] != [])
    if(stack[top] is a terminal t)
        if(t == tokens[i++])
            pop();
        else
            error(...);
    else if(stack[top] is a nonterminal T)
             pop();
             push(table[T, tokens[i]])

如何判断文法是LL(1)文法 求出该文法的first集、follow集和select集, 通过select集之间的关系进行判断

graph TB
S0{判断是否是LL1文法}
S1[消除文法左递归]
S2[消除文法的回溯]
S3[计算所有非终结符的FIRST集]
S4[计算所有非终结符的FLLOW集]
S5[根据FIRST和FLLOW集生成分析表]
S6[进行LL1分析]

SA((开始))
SS((结束))

SA-->S0
S0--是-->S1
S0--否-->SS
S1-->S2-->S3-->S4-->S5-->S6-->SS

那么就可以得到一个没有回溯的分析算法,但是怎么得到这个分析表呢?

移进-规约算法

.为了方便标记语法分析器已经读入可多少输入,我们可以引入一个点记号,来表示为读入的数据

  • 需要两个步骤
    • 移进一个记号到栈顶上
    • 规约栈顶上的n个符号到左部的非终结符
      • 对于产生式 $A$ -> $\beta$1 … $\beta$n,如果可以推导,那么弹出$\beta$1 … $\beta$n,
      • 压入非终结符
  • 如何确定移进-规约的时机

相关内容