编译原理MOOC笔记

本文详细介绍了编译原理中的词法分析和语法分析过程,包括正规文法、正则表达式、有限状态自动机、正则表达式到非确定有限状态自动机的转换,以及自上而下和自下而上的语法分析方法,如LL(1)和LR分析法。此外,还探讨了消除左递归和回溯、预测分析程序的构建等关键概念。


正规文法和正规式

正规式即正则表达式

正规文法为左线性或右线性文法

正规式和正规文法的定义

正规式转换为正规文法

正规文法转换为正规式


词法分析

源程序 -> 前端 -> 中间表示 -> 后端 -> 目标程序

前端

源程序 -> 词法分析器 -> 记号 -> 语法分析器 -> 抽象语法树 -> 语义分析器 -> 中间表示


词法分析器的任务

任务:字符流到记号流

字符流:和被编译的语言密切相关(ASCII,Unicode,or…)
记号流:编译器内部定义的数据结构,编码所识别出的词法单元

词法分析器的实现方法

  1. 手工编码实现法
    相对复杂、且容易出错,但是是非常流行的实现方法(现在的编译器如GCC和LLVM就是手工编码)

  2. 词法分析器的生成器
    快速、代码量少,但是难以控制细节

正则表达式

给定字符集 ∑ = c 1 , c 2 , . . . , c n \sum={c_1,c_2,...,c_n} =c1,c2,...,cn
归纳定义:
1. 空串 ϵ \epsilon ϵ 是正则表达式
2. 对于任意 c ∈ ∑ c\in\sum c, c是正则表达式
3. 如果M和N是正则表达式,则以下也是正则表达式
选择 M ∣ N = { M , N } M | N= \{M, N\} MN={M,N}
连接 M N = { m n ∣ m ∈ M , n ∈ N } MN = \{mn | m\in M,n \in N \} MN={mnmM,nN}
闭包 M ∗ = { ϵ , M , M M , M M M , . . . } M*=\{\epsilon , M , MM, MMM, ...\} M={ϵ,M,MM,MMM,...}

有限状态自动机

自动机={待补充}

字符串分为 可被自动机接受的字符串 和 不可被自动机接受的字符串

可被自动机接受:可达到结束状态
不可被自动机接受:不可达到结束状态

确定的有限状态自动机:对任意字符,最多有一个状态可以转移
不确定的有限状态自动机:对任意的字符,有多于一个状态可以转移

正则表达式到非确定有限状态自动机

词法分析器自动生成的过程:

R E ( 正 则 表 达 式 ) → T h o m p s o n 算 法 N F A → 子 集 构 造 算 法 D F A → H o p c r o f t 最 小 化 算 法 词 法 分 析 器 代 码 RE(正则表达式) \xrightarrow[]{Thompson算法} NFA \xrightarrow[]{子集构造算法} DFA \xrightarrow[]{Hopcroft最小化算法} 词法分析器代码 REThompson NFA DFAHopcroft

  • RE -> NFA (正则表达式到非有限状态的自动机): Thompson 算法
    基于对RE的结构做归纳
    – 对基本的RE直接构造
    – 对符合的RE递归构造

转换的方法如下:
RE2NFA

  • NFA -> DFA:子集构造算法
    子集构造算法
    上面的D[q,c]表示在状态q下,经过字符c能达到的状态

    时间复杂度:最坏 O ( 2 N ) O(2^N) O(2N),N为结点个数
    假如有N个结点,那么状态集合Q最多有 2 N 2^N 2N 个状态

  • DFA -> 词法分析器代码:Hopcroft算法(最小化DFA)、DFA的代码表示
    Hopcroft算法
    Hopcroft算法可以最小化DFA
    上面算法中的N,A分别代表非终止节点和终止节点。
    判断一个字符能否划分等价类,就是看这个字符是否能把一个等价类中的节点转换到该等价类之外的节点(但是如果该字符把该集合内所有节点都转化到该等价类之外的相同节点,那么就停止划分)
    例子:
    Hopcraft算法例子

  • DFA的代码表示
    – 转移表(类似于邻接矩阵)
    – 哈希表
    – 跳转表

转移表的实现

跳转表



语法分析

上下文无关文法可以用来描述语法规则

语法分析的任务:分析一个文法的句子结构

语法分析器的功能
按照文法的产生式(语言的语法规则),识别输入符号串是否为一个句子 (合式程序)

语法分析的方法:自上而下自下而上

自上而下的语法分析

构造不带回溯的自上而下分析的文法条件
在这里插入图片描述

构造不带回溯的自上而下分析算法的步骤

  1. 消除文法的左递归
  2. 提取左公共因子,克服回溯

LL(1)文法的条件: 需计算FIRST,FOLLOW集合

直接左递归的消除

直接左递归的消除

间接左递归的消除

间接左递归的情况:
间接左递归的消除
一个文法消除左递归的条件:
文法消除左递归的条件
消除间接左递归的算法:
消除间接左递归
例子:
在这里插入图片描述

消除回溯

FIRST集合及其构造:
FIRST集合
在这里插入图片描述
FOLLOW集合:
在这里插入图片描述

使得所有非终结符的候选首符集两两不相交的做法:提取左公共因子
提取左公共因子
LL1分析法:
LL1分析法

递归下降子程序

递归下降子程序


预测分析程序

预测分析程序的三个组成部分:

  1. 总控程序:根据现行栈顶符号和当前输入符号,执行动作
  2. 分析表 M [ A , a ] M[A,a] M[A,a] 矩阵, A ∈ V N A \in V_N AVN, a ∈ V T a \in V_T aVT是终结符或 ‘#’
  3. 分析栈STACK用于存放文法符号
    在这里插入图片描述

分析开始时分析栈中只有文法的开始符号S
在这里插入图片描述
在这里插入图片描述

构造预测分析表:在这里插入图片描述

注意

  1. 如果G是左递归或二义的,那么, M至少含有一个多重定义入口。因此,消除左递归和提取左因子将有助于获得无多重定义的分析表M。
  2. y一个文法G的预测分析表M不含多重定义入口,当且仅当该文法为LL(1)的。

LL(1)文法不是二义的

自下而上分析

过程:

  • 从输入串开始,逐步进行归约,直到文法的开始符号
  • 归约:根据文法的产生式规则,把串中出现的产生式的右部替换成左部符号
  • 从树叶节点开始,构造语法树
  • 算符优先分析法、LR分析法(这两个是自下而上的分析方法)

基本思想:

  • 采用移进-归约的思想进行自下而上分析
    (这里的移进指的是移进栈里)
  • 用一个寄存符号的先进后出栈,把输入符号一个一个地移进到栈里,当栈顶形成某个产生式地候选式时,即把栈顶地这一部分替换成(归约为)该产生式的左部符号

核心问题:

  • 识别可归约串
    (可归约串是短语)

短语和直接短语

短语的定义:
令G是一个文法,S是文法的开始符号,假定 α β δ \alpha\beta\delta αβδ是文法G的一个句型,如果有
S ⇒ ∗ α A δ , 且 A ⇒ + β S \stackrel{*}{\Rightarrow}\alpha A \delta ,且 A\stackrel{+}{\Rightarrow}\beta SαAδA+β
则称 β \beta β 是句型 α β δ \alpha\beta\delta αβδ 相对于非终结符A的短语
如果有 A ⇒ β A\Rightarrow\beta Aβ,则称 β \beta β是句型 α β δ \alpha \beta \delta αβδ 相对于规则 A ⇒ β A \Rightarrow \beta Aβ的直接短语

判断短语和直接短语的例子:
短语和直接短语

(算符文法)算符优先分析方法:

算符优先分析法:

  • 按照算符的优先关系和结合性质进行语法分析
  • 适合分析表达式

任何两个可能相继出现的终结符a与b可能三种优先关系

  1. a ⋖ b a \lessdot b ab a的优先级低于b
  2. a ≖ b a \eqcirc b ab a的优先级低于b
  3. a ⋗ b a \gtrdot b ab a的优先级高于b
    注意:算符优先关系与数学上的<、>、=不同
    例如:
    a ⋖ b a \lessdot b ab 并不意味着 b ⋗ a b \gtrdot a ba

算符文法:
一个文法,如果它的任一产生式的右部都不含两个相继(并列)的非终结符,即不含…QR…形式的产生式右部,则我们称该文法为算符文法

算法优先文法:
算符文法的定义

构造优先关系表的算法——FIRSTVT和LASTVT集合

在这里插入图片描述
构造FIRSTVT§的算法:
在这里插入图片描述
算法的实现:
在这里插入图片描述
在这里插入图片描述

对应的,我们有构造LASTVT§的算法:
在这里插入图片描述
构造优先关系表:
构造优先关系表的算法
算符优先分析算法:
一个文法G的句型的素短语是指这样一个短语,它至少包含一个终结符,并且,除它自身之外不再含任何更小的素短语
最左素短语是指处于句型最左边的那个素短语

最左素短语和素短语的识别:
在这里插入图片描述

最左素短语定理:
在这里插入图片描述

算符优先分析算法的结果不一定是语法树

算符优先分析法
缺点:可能错误接受非法句子
优点:简单、快速



句柄和规范归约(LR分析法)

规范归约:句柄作为可归约串
L:从左到右扫描输入串
R:自下而上进行归约

句柄的定义:一个句型的最左直接短语
句型:推导过程中产生的产生式
句子:只有终结符的句型为句子
句子和句型的区别
句柄
用句柄进行归约:
句柄归约
规范归约的定义:(即使用句柄进行归约)
在这里插入图片描述

规范句型

  • 规范归约是最左归约
  • 规范归约的逆过程就是最右推导
    S ⇒ a A c B e ⇒ a A c d e ⇒ a A b c d e ⇒ a b b c d e S\Rightarrow aAcBe \Rightarrow aAcde \Rightarrow aAbcde \Rightarrow abbcde SaAcBeaAcdeaAbcdeabbcde
  • 最右推导也称为规范推导
  • 由规范推导推出的句型称为规范句型

规范归约的关键:寻找句柄



LR分析法的核心:LR分析表
LR分析表包含:

  • A C T I O N [ s , a ] ACTION[s,a] ACTION[s,a] G O T O [ s , X ] GOTO[s, X] GOTO[s,X]
  • G O T O [ s , X ] GOTO[s,X] GOTO[s,X] 是在归约之后使用的
    在LR分析表

LR分析表包含四个动作:移进归约接受报错
移进
归约

接受
报错

LR文法的定义:
对于一个文法,如果能够构造一张分析表,使得它的每个入口均是唯一确定的,则这个文法就称为LR文法


LR(k) 文法的定义:
一个文法,如果能用一个每步顶多向前检查k个输入符号的LR分析器进行分析,则这个文法就称为LR(k) 文法


规范归约过程中:

  • 栈内符号串和扫描剩下的输入符号串构成了一个规范句型
  • 栈内的如果出现句柄,句柄一定在栈的顶部
  • 栈内永远不会出现句柄之后的符号

活前缀

字的前缀、活前缀:

  • 字的前缀:是指字的任意首部,如字abc的前缀有 ϵ , a , a b , a b c \epsilon, a, ab, abc ϵ,a,ab,abc
  • 活前缀是指规范句型的一个前缀, 这种前缀不含句柄之后的任何符号。即,对于规范句型 α β δ \alpha \beta \delta αβδ β \beta β 为句柄,如果 α β = u 1 u 2 . . . u r \alpha \beta = u_1u_2...u_r αβ=u1u2...ur,则符号串 u 1 u 2 . . . u i ( 1 < = i < = r u_1u_2...u_i (1<=i <=r u1u2...ui(1<=i<=r α β δ \alpha \beta \delta αβδ的或前缀 ( δ \delta δ必为终结符串)
  • 规范归约过程中,保证分析栈中总是活前缀,就说明分析采取的移进/归约动作是正确的
识别活前缀

拓广文法:
拓广文法


LR(0)项目:
在这里插入图片描述

构造识别文法所有活前缀的DFA
(项目 -> NFA -> DFA)

在这里插入图片描述
识别活前缀的NFA
识别活前缀的DFA

LR(0)项目集(项目集就是状态的意思)规范族:
在这里插入图片描述

通过计算项目集规范族构造识别活前缀的DFA

有效项目
有效项目
有效项目的性质
有效项目的性质

项目集的闭包CLOSURE:
假定I是文法 G ′ G' G的任一项目集,定义和构造I的闭包CLOSURE(I)如下:
在这里插入图片描述

项目集的转移函数

项目集的转移函数的计算:
在这里插入图片描述

LR(0)项目集规范族的构造算法
在这里插入图片描述

相中构造识别活前缀的DFA的方法:

  1. 项目 -> NFA -> DFA
  2. closure -> GO -> DFA
(从DFA)构造LR(0)分析表

LR(0)文法
LR(0)分析表的构造

LR(0)分析表的ACTION和GOTO子表构造
在这里插入图片描述
在这里插入图片描述

在规范归约过程中,只要保证分析栈中总是活前缀,就说明分析采取的移进/归约动作是正确的

一个非LR(0)文法

一个非LR(0)文法:例如含有“移进-归约”冲突的项目

SLR(1)分析法

SLR(1)冲突解决办法
在这里插入图片描述

SLR(1)里的1指最多向前看1个单词


SLR(1)分析表的构造

构造SLR(1)分析表的方法:
构造SLR1分析表的方法

SLR(1) 和 LR(0) 分析表构造方法的对比(在这里插入图片描述

按上述方法构造出的ACTION与GOTO表如果不含多重入口,则称该文法为SLR(1)文法
使用SLR表的分析器叫做SLR分析器
每隔SLR(1)文法都是无二义的。但也存在许多无二义文法不是SLR(1)的。
L R ( 0 ) ⊂ S L R ( 1 ) ⊂ 无 二 义 文 法 LR(0) \subset SLR(1) \subset 无二义文法 LR(0)SLR(1)


一个非SLR(1)文法

SLR冲突消解存在的问题
在这里插入图片描述

LR(1)分析表的构造

构造LR(1)分析表的方法

LR(k)项目:
LR(k)项目
有效项目的定义因此可以这样写:
在这里插入图片描述

有效项目的性质:
有效项目的性质
构造LR(1)项目集规范族包含两部分:

  • 闭包函数CLOSURE
  • 转换函数GO

项目集的闭包CLOSURE的计算
项目集的闭包CLOSURE的计算
项目集的转换函数GO
转换函数
在这里插入图片描述
在这里插入图片描述
LR(1)和SLR(1)分析表构造方法的对比
LR(1)和SLR(1)分析表构造方法的对比

LR(1)分析表的构造
在这里插入图片描述

语义分析与中间代码生成器

属性文法

属性文法:也称属性翻译文法
以上下文无关文法为基础

  • 为每个文法符号(终结符或非终结符)配备若干相关的“值”(称为属性),代表与文法符号相关信息,如类型、值、代码序列、符号表内容等
  • 对于文法的每个产生式都配备了一组属性的语义规则,对属性进行计算和传递

综合属性
自下而上传递信息
语法规则:根据右部候选式中的符号的属性计算左部被定义符号的综合属性
语法树:根据子节点的属性和父结点自身的属性计算父结点的综合属性

继承属性
自上而下传递信息
语法规则:根据右部候选式中的符号的属性和左部被定义符号的属性计算右部候选式中的符号的继承属性
语法树:根据父结点和兄弟节点的属性计算子节点的继承属性



属性依赖(带注释的语法树)

对应于每个产生式 A − > α A->\alpha A>α 都有一套与之相关联的语义规则,每条规则的形式为(f是一个函数):
b : = f ( c 1 , c 2 , . . . , c k ) b:=f(c_1, c_2, ..., c_k) b:=f(c1,c2,...,ck)

属性b依赖于属性 c 1 , c 2 , . . . , c k c_1, c_2, ..., c_k c1,c2,...,ck

  • b是综合属性如果b是A的属性
  • b是继承属性,如果b是产生式右边某个文法符号的属性

终结符只有综合属性,由词法分析器提供

非终结符即可有综合属性也可有继承属性,文法开始符号的所有继承属性作为属性计算前的初始值

在这里插入图片描述
出现在产生式左边的继承属性出现在产生式右边的综合属性不由所给的产生式的属性计算规则进行计算,由其它产生式的属性规则计算或者由属性计算器的参数提供,

翻译一下上面这段话的意思是:左边文法符号只能算综合属性,不能算继承属性,右边的文法符号只能算继承属性不能算综合属性

带注释的语法树

在语法树中,一个节点的综合属性的值由其子结点和它本身的属性值确定

使用自底向上的方法在每一个结点处使用语义规则计算综合属性的值

仅使用综合属性的属性文法称S - 属性文法

语法制导翻译法:由源程序的语法结构所驱动的处理办法就是语法制导翻译法

输 入 串 − > 语 法 树 − > 按 照 语 义 规 则 计 算 属 性 输入串 -> 语法树 -> 按照语义规则计算属性 >>

基于属性文法的处理方法包括:

  • 依赖图
  • 树遍历
  • 一遍扫描

依赖图

在一颗语法树中的节点的继承属性和综合属性之间的相互依赖关系可以由依赖图(有向图)来描述

为每一个包含过程调用的语义规则引入一个虚综合属性b,这样把每一个语义规则都写成
b : = f ( c 1 , c 2 , c . . . , c k ) b:=f(c_1, c_2, c..., c_k) b:=f(c1,c2,c...,ck)
的形式

依赖图中为每一个属性设置一个结点,如果属性b依赖于属性c,则从属性c的结点有一条有向边连接到属性b的节点

依赖图的示例:
良定义的属性文法
良定义的属性文法:
如果一属性文法不存在属性之间的循环依赖关系,则称该文法为良定义的
一个依赖图的任何拓扑排序都给出一个语法树中结点的语义规则计算的有效顺序

属性的计算次序:
在这里插入图片描述

树遍历

树遍历
树遍历


一遍扫描

在语法分析的同时计算属性值

语义规则被计算的时机:

  • 对于自上而下分析,是在一个产生式匹配输入串成功时
  • 对于自下而上分析,是在一个产生式被用于进行归约时

抽象语法树:
在语法树中去掉那些对翻译不必要的信息,从而获得更有效的源程序中间表示


S属性文法

S-属性文法:只含有综合属性

在自下而上的分析器分析输入符号串的同时计算综合属性

分析栈中保存语法符号和有关的综合属性值

每当进行归约时,新的语法符号的属性值就由栈中正在归约的产生式右边符号的属性值来计算
S-属性文法的自下而上的计算

L属性文法

与LL(1)自上而下分析方法结合

  • 深度优先建立语法树
  • 按照语义规则计算属性

L-属性文法的定义:
L-属性文法

翻译模式

翻译模式:给出使用语义规则进行计算的次序,把实现细节表示出来

在翻译模式中,和文法符号相关的属性和语义规则(也称语义动作),用花括号{}括起来,插入到产生式右部的合是位置上

设计翻译模式时,必须保证当某个动作引用一个属性时它必须是有定义的
L-属性文法本身就能确保每个动作不会引用尚未计算出来的属性

当只需要综合属性时,为每一个语义规则建立一个包含赋值的动作,并把这个动作放在相应的产生式右边的末尾,如下所示:
只包含综合属性
建立翻译模式:
如果既有综合属性又有继承属性,在建立翻译模式时就必须保证:
建立翻译模式

语义动作执行时机统一

把所有的语义动作都放在产生式的末尾
建立翻译模式

消除翻译模式中的左递归

消除翻译模式中的左递归

一般化的左递归消除:
在这里插入图片描述

递归下降翻译器的设计

分析程序由一组递归子程序(函数)组成,每个非终结符对应一个子程序(函数)
在这里插入图片描述
在这里插入图片描述

递归下降翻译器的设计
递归下降翻译器的设计

语音分析和中间代码生成

中间语言的特点与运用

中间语言
引入中间语言的优点:

  • 使编译程序的结构在逻辑上更为简单明确
  • 便于进行与机器无关的代码优化工作
  • 易于移植

常用的中间语言:

  • 后缀式
  • 图表示:抽象语法树(AST)、有向无环图
  • 三地址代码:三元式、四元式、间接三元式

后缀式:
所有操作符置于操作数后面

图表示法:
抽象语法数(AST)
有向无环图(DAG)

三地址代码
三元式
缺点:不易于优化,因为当需要改变式子的位置的时候,下标也要变

于是引入了方便优化的三元式

间接三元式:
三元式表 + 间接码表
间接码表:一张指示器表,按运算的先后次序列出有关三元式在三元式表中的位置
三元式表

赋值语句的翻译

简单算术表达式及赋值语句

赋值语句的形式和意义


赋值语句生成三地址代码的S-属性文法
赋值语句生成三地址代码的S-属性文法

赋值语句生成三地址代码的S-属性文法
发现规律:都是先制造E.place,然后制造 E.code

赋值语句的翻译模式:

产生赋值语句三地址代码的翻译模式
三地址代码的翻译模式
三地址代码的翻译模式
发现规律:只对E.place进行操作,如果需要进行运算,则需要进行newtemp


数组元素的引用

数组元素地址计算

数组元素的地址计算
数组元素地址计算


产生有关类型转换的指令

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

布尔表达式及其计算

带优化的翻译法

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值