复习要求:翻看笔记+高珍老师PPT,然后挑重点理解,要记得刷题
- 绪论
- 高级语言及其文法
- 词法分析
- 语法分析
- 语法制导翻译
- 中间代码生成
- 优化
视频:
- 绪论 1-6
- 语言及其文法 2.5完整看完
- 你
- chapter 9
考点用重点考点
进行标识
需要补充的地方[?]
参考网址:编译原理MOOC笔记
绪论
课件PPT
什么是编译
计算机程序语言可以自顶向下可以分为高级语言、汇编语言和机器语言三种,其中开发程序员最长接触的为高级语言,如JAVA,高级语言经过编译就会生成汇编语言或机器语言,具体关系如下图:
编译的过程就是将高级语言
翻译成汇编语言或机器语言
的过程 ,即将源语言
转化为目标语言
的过程。
编译器在语言处理系统中的位置如下图所示:
可重定位(Relocatable): 在内存中存放的起始位置L不是固定的
加载器:修改可重定位地址;将修改后的指令和数据放到内存中适当的位置
起始位置+相对地址=绝对地址 链接器
的作用:- 将多个可重定位的机器代码文件(包括库文件)
- 连接到一起解决外部内存地址问题
编译系统的结构
在有了高级语言程序之后(如一段C语言代码),编译器应该怎么翻译成汇编语言程序或机器语言程序呢?
我们先来看一下人工英汉怎么进行翻译的,编译器所做的工作有类似之处。
首先我们分析源语言,即英文,先拆分名词、动词等,然后组成短语,最终才能组合成句。即一段语言的分析理解包括了3个基本的小部分
,最终才能连接成为一个完整的句子
- 词法分析(Lexical Analysis)
- 语法分析(Syntax Analysis)
- 语义分析(Semantic Analysis)
那么一个完整的编译器结构如下图所示:
词法分析概述
词法分析顾名思义就是通过扫描源程序,识别出每一个单词,确定单词类型,然后转化为统一的机内表示——词法单元(token)
形式。
token:< 种别码,属性值>
例如if、else对应唯一的表示形式。具体的表示形式如下表所示:
例如有一句代码为:
while(value!=100){num++;} |
则经过词法分析后如下所示,其中每句后的<-,->表示每个单词的含义。
while < WHILE , - > // 关键词,一词一码 |
语法分析概述
语法分析器(parser)从词法分析器输出的token序列中识别出各类短语,并构造语法分析树(parsetree)
。简而言之就是将词法分析后识别出来的单词构成短语,将短语整合成一个完整的句子。
语法分析树描述了句子的语法结构。如下是赋值语句
的语法分析树:
position = initial + rate * 60 ; |
例如我们需要得到变量声明的分析树
,变量声明可以是int a;也可以是int a,b,c;抽象成文法(变量声明的规则)和分析树为:
语义分析概述
语义分析的主要任务是收集标识符的属性信息
,其中包括种属(简单变量、复合变量···)、类型(整型、实型···)、存储位置、长度等;除此之外语义分析还要进行语义检查
,其中包括变量是否是没有声明就使用了。
中间代码生成及编译器后端概述
常见的中间表示形式为:
三地址码(Three-address Code)
:三地址码由类似于汇编语言的指令序列组成, 每个指令最多有三个操作数(operand)。语法结构树/语法树(Syntax Trees)
常见的为三地址形式,其中表示方法为四元式(Quadruples)
:(op, y, z, x)。
中间代码生成的案例为:
[!!!重点题]先画出语法分析树,然后再进行分析
语言及其文法
基本概念
正如英语是由句子组成的集合,而句子又是由单词和标点符号组成的序列那样。程序设计语言 C 语言,是由 C 程序所组成的集合,而程序是由类似 if , begin, end 的符号,字母和数字这样一些基本符号所组成。
从字面上看,每个程序都是一个“基本符号”串,设有一基本符号串,那么 C 语言可看成是在这个基本符号集上定义的,按一定规则构成的一切基本符号串组成的集合。
字母表
字母表$$\sum$$是一个有穷的符号集合,其中符号包括字母、数字和标点符号等
如:
二进制字母表:{ 0,1 } ;
ASCII字符集 ;
Unicode字符。
字母表上的运算
字母表 $\sum_1$ 和 $\sum_2$ 的
乘积
( product)
字母表 $\sum$ 的
n次幂
( power):长度为n的符号串构成的集合
字母表 $\sum$ 的
正闭包
( positive closure):长度为正数的符号串构成的集合
字母表 $\sum$ 的克林闭包(Kleene closure):任意符号串(
跟正闭包相比长度可以为零
)构成的集合
符号串
由字母表中的符号组成的任何有穷序列称为符号串。
串 s 的长度,通常记作 |s| ,是指 s 中符号的个数。例:|aab|=3。
空串是长度为 0 的串,用 ε 表示,|ε|=0。
串上的运算
串的连接
如果 x 和 y 是串,那么 x 和 y 的连接,是把 y 附加到 x 后面而形成的串,记作 xy。
例如,如果 x=dog 且 y=house,那么 xy=doghouse。
空串是连接运算的单位元,即,对于任何串 s 都有, εs=sε=s。
串的幂运算:将 n 个 串连接起来。
文法的定义
自然语言中句子的构成规则为:句子由名词短语构和动词短语成,名词短语由形容词和名词短语构成···,直到最终特定的名词、动词、形容词等。
分析一个自然语言的例子,得出句子的构成规则。
文法的形式化定义
$$
G = (V_T , V_N , P , S )
$$
G:表示文法,文法由一个四元组定义
终结符集合和非终结符集合都是字母表
终结符集合与非终结符集合是不相交的 -> $V_T∩V_N=Φ$
终结符集合与非终结符集合的并集是文法符号集 -> $V_T∪V_N$:文法符号集
$V_T$:
$V_N$:因为从它们可以推出其他的语法成分,所以被称为非终结符
P:产生式集合,注意左部和右部的取值,左部是正闭包,右部是克林闭包
例: P = <句子> → <名词短语><动词短语>, <名词短语> → <形容词><名词短语>
S:开始符号
$S∈V_N$,开始符号(start symbol)表示的是该文法中最大的语法成分。例:S = <句子>
例:
产生式的简写
符号的约定
终结符
非终结符
其他
语言的定义
推导与规约
假设自然语言的文法可以表示为
单词串:little boy eats apple
有了文法(语言规则),如何判定一个单词串是否是满足文法的句子?
答案是:推导和规约。
$ a_0$ 经过 n 步推导出$ a_n$ ,可简记为:$ a_0 -> a_n$
例:
由上而下为推导,即具体化的过程——-从生成语言的角度
由下而上为规约,即抽象化的过程——从识别语言的角度
句型和句子
句型:
句子:句子是不包含非终结符的句型
例:
语言的形式化定义
由文法 G 的开始符号 S 推导出的所有句子
构成的集合称为文法G生成的语言
,记作 L(G),即:`
例1:
$E->E+E | E * E | (E) | id$ 生成的语言中包含多少个句子
答案是无数个,文法解决了无穷语言的有穷表示问题
例2:
我们现在拥有的文法 G 如下所示,其生成的语言标识符,即以字母开头的字符串。因为S定义为一个字母或以字母开头的字母数字串,即用于表示标识符。
语言上的运算
文法的分类
0型文法,无限制文法,短语结构文法
- α中至少包含1个非终结符
1型文法,上下文有关文法(CSG)
产生式左部符号的个数不能多于右部
CSG不包含 $\varepsilon$ 产生式,即产生式右部是空串的产生式,因为左部至少包含一个非终结符,左部长度至少为1,如果右部是 $\varepsilon$ ,右部长度为1,与CGS定义不符合。
2型文法,上下文无关文法(CFG)
- 左边是一个非终结符,将其替换不需要考虑其上下文
- 所举的例子即2型文法
3型文法,正规文法,正则文法(RG)
- 3型文法分为2种文法
- 产生式右部最多只有一个非终结符,且只能在一侧
- 例子中的两个文法都是指标识符,即一个字母或以字母开头的字母数字串
- 程序语言中的多数单词都能用正则文法表示
四种文法之间的关系
上下文无关文法的分析树
程序语言中的多数单词都能用正则文法表示,但正则文法生成能力有限,句子构造则需要用上下文无关文法进行描述。
CFG分析树定义
例:图中跟节点表示的是对第三个式子的应用
分析树是推导的图形化表示
推导过程中产生许多句型
,最终推导出分析树的边缘
句型的短语
给定一个句型,其分析树中的每一颗子树的边缘称为该句型的一个短语
。
如果子树只有父子两代节点,那么这课子树的边缘称为该句型的一个直接短语
。
直接短语一定是产生式的右部,但产生式的右部不一定是给定句型的直接短语,但可能是其他句型的直接短语
例:人民、生活、水平是该句子的直接短语,而高人、民生、活水虽然也是第5个产生式的右部,但是在这棵分析树中,它们不是直接短语。
二义性文法
如果一个文法可以为某个句子生成多棵分析树,则称这个文法是二义性的。
下面这个例子产生二义的原因是else可以跟第一个if条件语句和第二个if条件语句相配。
消歧规则
:每个else和最近的尚未匹配的if进行匹配,所以只有第一个分析树
二义性文法的判定
对于任意一个上下文无关文法,不存在一个算法,判断它是无二义性的,但能给出一组充分条件,满足这组充分条件的文法是无二义性的。
- 满足,肯定无二义性
- 不满足,也未必就是有二义性的
词法分析
词法分析这一章从正则表达式到有穷自动机,讲述了如何利用确定性的有穷自动机来进行单词的识别。其中正则表达式和有穷自动机之间也有相应的转化关系。
本文内容:介绍正则定义,正则表达式,有穷自动机(确定的有穷自动机 DFA,不确定的有穷自动机 NFA),NFA 转换为等价的 DFA,DFA 的化简,识别单词的 DFA ,典型例题及详细解答。
在前面我们说过,程序设计语言中的大多数单词都可以用正则文法来描述,在这一章中我们将介绍描述正则语言的更紧凑的方法——正则表达式。
正则表达式(RE)
语言是一个集合,因此我们可以在语言上进行多种集合运算。比如说并运算,乘积运算(即连接运算),闭包运算等等。
正则表达式示例
接下来我们看一个语言的例子,如下图所示:
这个语言的字首是字母 a,接下来连接一个任意长度的 a,b 串,再接下来连接一个空串。连接一个空串就代表句子已经结束了。除此之外,我们还可以连接一个点号(.)或者下划线(_)或者一个长度大于等于1 的 a,b串。
这个式子写起来比较复杂,因此我们要介绍正则表达式。
正则表达式(Regular Expression,RE)是一种用来描述正则语言的更紧凑的表达方式。
例如上面的语言可以用正则表达式来表示,如上图所示。
这个正则表达式表示,句子的第一个符号是字母 a ,接下来连接一个任意长度的 a,b串,再接下来连接一个空串。连接一个空串就代表句子已经结束了。除此之外,我们还可以连接一个点号(.)或者下划线(_)或者一个长度大于等于1 的 a,b 串。
从这个例子中,我们可以看出,正则表达式可以用较小的正则表达式根据特定规则来递归
构建。
每个正则表达式 r 定义(表示)一个语言,记为 $L(r)$。这个语言也是根据 r 的子表达式所表示的语言递归
定义的。
正则表达式的定义
- 空串是一个正则表达式,那么它所表达的语言也只包括空串。
- 字母表上的任何一个符号都是一个正则表达式,那么它所表示的语言只包含它本身。
- 假设 r 和 s 都是正则表达式,它们表示的语言分别是 $L(r)$ 和 $L(s)$ ,则:
- $r|s$ (r 或 s)也是一个正则表达式,它表示的语言是 $L(r|s) = L(r) ∪ L(s)$
- rs(r 和 s 的连接)也是一个正则表达式,它表示的语言是 $L(rs) = L(r)L(s)$
- $r^$(r 的克林闭包)也是一个正则表达式,它表示的语言是也 $L(r^) = (L(r))^*$
- $(r)$ 是一个正则表达式,它表示的语言就是 $L(r)$,即 $L((r))=L(r)$
例:
假设符号表中有 a,b。则 a 是一个正则表达式,b 也是一个正则表达式。可以推导出以下的正则表达式:
例:
描述 C 语言中无符号整数的正则表达式
- 十进制整数的正则表达式:第一个符号是1
9中的一个数字,接下来连接若干个 09 的数字,或者连接符号 0。 - 八进制整数的正则表达式:第一个符号是数字0,第二个符号是1
7中的一个数字,接下来连接若干个 07 中的数字。 - 十六进制整数的正则表达式:第一个符号是0,第二个符号是 x,第三个符号是 1~f 中的符号,接下来连接若干个 0~f 中的符号。
正则表达式的代数定律
正则语言:可以用正则表达式定义的语言叫做正则语言或正则集合。
正则表达式也遵循一些代数定律,如下图所示:
正则文法与正则表达式等价:
对于任何一个正则文法 G,存在定义同一语言的正则表达式 r。
对任何正则表达式 r,存在生成同一语言的正则文法 G。
总之,有 G 就有 r ,有 r 就有 G 。
正则定义
为了方便起见,我们可以给某些正则表达式命名
,像使用字母表中的符号一样,使用这些名字来构造正则表达式。(这就是正则定义提出的定义和基本思想)
正则定义就是给正则表达式命名,右侧是字母表与已定义的正则定义的并集
例1:C语言的标识符
C语言中标识符的正则定义:
第一个正则表达式,表示0~9中的某个数字,我们给它取一个名字,digit
第二个正则表示式,表示一个字母(小写字母或大写字母)和一个下划线。我们给它取一个名字,letter_
接下来我们用起好的这两个名字,来构造第三个正则表达式。
第三个正则表示式,首先是一个 letter_,接下来连接一个 letter _ 或 digit 构成的字符串。这个表达式表示的是字母打头的字符数字串。(正是标识符的定义)
例2:无符号数
(整型或浮点数)无符号数的正则定义:
digit,还是表示一个数字
digits,digit 连接上一个 digit 的克林闭包,表示的是一个长度>=1 的数字串。
optionalFraction,点号(.)后面连接一个 digits 或 这个表达式是一个空串。(这个符号表示的是一个小数部分,或一个空串)代表可选的小数部分。
optionalExponent,大写字母 E 后面连接一个 + (正号)或 一个 -(负号)或直接连接一个长度大于等于1 的数字串(digits),或者这个表达式为空串。可选的指数部分。
number,长度大于等于1 的数字串,连接一个可选的小数部分,连接一个可选的指数部分。
当可选的小数部分为空串时,这个表达式为一个整数的若干次幂,如 2E-3
若可选的指数部分为空串时,这个正则表达式为小数。比如说 2.15若可选的小数部分和可选的指数部分都为空串时,这个正则表达式为整数。比如说 2
若可选的小数部分和可选的指数部分都不为空串时,这个正则表达式为指数形式的浮点数。比如说 2.15E+3, 2.15E-3。
当指数为正号时,指数是可以省略的,如 2.15E3
有穷自动机(FA)
有穷自动机(Finite Automata,FA)由两位神经物理学家Meculoch 和 Pitts 于 1948年提出,是对一类处理系统建立的数学模型。
重要的理论基础
这类系统具有一系列离散的输入输出信息和有穷数目的内部状态
。
系统只需要根据当前所处的状态和当前面临的输入信息,就可以决定系统的后继行为。每当系统处理了当前的输入后,系统的内部状态也将发生改变。
FA 的典型例子
电梯控制装置
- 输入:顾客的乘梯需求(所要到达的层号)
- 状态:电梯所处的层数+运动方向
- 电梯控制装置并不需要记住先前全部的服务要求,只需要知道电梯当前所处的状态以及还没有满足的所有服务请求。
FA 模型
FA模型由以下三部分组成:
输入带:用来存放输入符号串
读头:从左向右逐个读取输入符号,不能修改(只读),不能往返移动
有穷控制器:具有有穷个状态数,根据当前的状态 + 当前输入符号控制转入下一状态。
状态图
有穷自动机可以用转换图来表示。
结点:FA 的状态
- 初始状态(开始状态):只有一个,由 start 箭头指向
- 终止状态:可以由多个,用双圈表示(下例中的 3)
带标记的有向边:如果对于输入 a ,存在一个从状态 p 到状态 q 的转换,就在 p、q 之间画一条有向边,并标记上 a。
在这个图中,一共有4个状态,分别为状态0,状态1,状态2,状态3。状态0为初始状态,由start箭头指向,状态3为终止状态,用双圈表示。
FA定义(接受)的语言
给定输入串 x,如果存在一个对应于串 x 的从初始状态到某个终止状态的转换序列,则称 串 x 被该 FA 接受。
由一个有穷自动机 M 接受的所有串构成的集合称为是该 FA定义(或接收)的语言,记为 $ L(M)$。
对于 abbaabb来说,我们可以判断是否为这个 FA 所接受。能。
接受第一个 a后,由初始状态0转换到状态 0,再遇到两个 b 后,依然保持状态 0,遇到下个 a 时,还保持状态 0,再遇到一个 a 时,转换到状态1,接下来两个 b,分别转换到状态2,和最终状态3。
最长子串匹配原则
当输入串的多个前缀与一个或多个模式匹配时,我们总是选择最长的前缀进行匹配。
对于上图来说,当遇到 < 号时,转换到状态1,当遇到 < = 号时,转换到状态2。
即:在到达某个终态
之后,只要输入带上还有符号,DFA 就继续前进,以便找到尽可能长的匹配。
有穷自动机的分类
- 确定的有穷自动机(DFA)
- 不确定的有穷自动机(NFA)
确定的有穷自动机 (DFA)
DFA定义为一个五元组
假设字母表中没有 $\varepsilon$
例:
在这个有穷自动机DFA 中,
状态集S 包含4个状态。分别是:状态0, 状态1, 状态2, 状态3。
输入字母表Σ 中包含的元素是:符号a,符号b。
转换函数 δ ,我们用一个转换表来表示。
例:状态0 遇到符号 a 时,变成状态1,状态0 遇到 符号 b 时,依旧是状态 0。以此类推,完成转换表(重点考点)
。
- 3是终止态,用 * 标识。
DFA可以用状态图或转换表两种等价方式来表示。
DFA 的算法实现
输入:以文件结束符 eof 结尾的字符串 x,DFA 的开始状态为 s0,接受状态集 F,转换函数 move
输出:如果 D 接受 x,则回答“yes”,否则回答“no”
方法:将下述算法应用于输入串 x
s=s0; |
非确定的FA (NFA)
DFA定义为一个五元组
同样假设字母表中没有 $\varepsilon$
NFA 与 DFA
唯一的区别
是:从状态 s 出发,能到达的状态可能有多个。(并不是唯一确定的)因此,转换函数为集合,而不是元素。
例:
在这个例子中,在初始状态0,遇到符号 a 的时候,它进入的状态包含状态0和状态1 ,两个元素。在状态0 时,遇到符号 b 时,它进入的状态只有 状态 0,因此集合中只有状态 0 一个元素。
如果转换函数 没有给出对应于状态-输入对的信息,就把空集放入到相应的表项中。
带有 ε边 的 NFA
在状态 a,不需要遇到任何符号,即可进入状态 b,在状态 b,不需要任何符号,即可进入状态 c。
一旦进入状态 b,就不再接受符号 0,同理,一旦进入状态 c,就不在接受符号 1。
这个带有 空边 的NFA 接受的语言是 由若干个 0 连接 若干个 1 再连接上 若干个 2。(r=0 * 1 * 2*)
带有 ε边 和 不带有 ε边 的 NFA的等价性
- 不带空边的状态 A:由若干个 0 构成
- 不带空边的状态 B:由若干个 0 连接若干个 1 构成
- 不带空边的状态 C:由若干个 0 连接 若干个 1 连接 若干个 2 构成
但是状态A,B,C 都可以概括为若干个 0 连接 若干个 1 再连接上 若干个 2 构成。
DFA和NFA的等价性(重点考点)
对任何非确定的有穷自动机 NFA,存在定义同一语言的确定的有穷自动机 DFA。
对任何确定的有穷自动机 DFA,存在定义同一语言的非确定的有穷自动机 NFA。
DFA 和 NFA 可以识别相同的语言
这两个 DFA 和 NFA 都识别的是以 abb结尾 的 a,b 串。
等价性
(重点考点)
(重点考点)
:正则文法(即3型文法,a->b形式) = 正则表达式(大概是字符串,正则表达式是正则语言更紧凑的表达方式) = 有穷自动机给定正则文法,可以构造等价的有穷自动机,给定有穷自动机,能够构造出相应的正则文法。
(重点)
:DFA和NFA都可以识别同一种语言,但就在表现形式而言,NFA比DFA更加直观,就计算机实现而言,NFA比DFA更难实现。从正则表达式构造NFA比较简单,然后再将NFA转换成DFA
带有 ε边 和 不带有 ε边 的 NFA也具有等价性
正则表达式和有穷自动机之间的转换
(重点考点)
记住几个常见的,然后把RE不停分解即可
正则表达式是采用符号序列的模式,它可以很直观的描述单词的构成。但在构造分析器时,我们真正实现和模拟的是 DFA。因此这涉及到从正则表达式到有穷自动机的转换。
从正则表达式到 DFA 的转换是比较困难的。所以我们通常是 将正则表达式 转换成 NFA ,再将 NFA 转换 成 DFA。
即:RE -> NFA -> DFA
从RE到NFA
正则表达式经过运算,仍然是正则表达式
不停的分解子表达式,即可求得最终的 NFA。
例:
将正则表达式 r=(a|b)*abb 对应的 NFA,不断地进行分解。
首先将 (a|b)* abb 分解成 4 个子表达式连接的形式。再将 (a|b)* 继续进行分解,最终得到最后结果。
从NFA到DFA的转换
(重点考点)
举例
例1:
NFA转换表 -> 根据表新的状态
从 NFA 转换到 DFA 时,我们要构造新的状态。
比如说,在初始状态 a ,遇到符号 a 时,可能继续保持 状态 a,也有可能转换到 状态 b。因此构造新的状态 a,b。
验证NFA和DFA是否等价:表达的正则表达式一样
DFA 的每个状态都是由 NFA 中的状态构成的集合,即 NFA 状态集合的一个子集。
例2:带有 ε 边 的 NFA 到 DFA 的转换
==注意转换表的生成==,A遇到0可以到状态ABC,遇到1可以到状态BC,遇到2可以到状态C.
转换表中有几个非空集合,最后DFA就有几条状态转移线->这是错误的结论,应该跟着它的思路去推导出来,这种题要重新自己做一遍
因为 状态 A 不需要任何输入,即可转换成状态 B,状态 C。所有在遇到输入 0时,它既可以是状态 A,也可以是 状态 B,状态 C。后面同理,即可得状态表。
![image-20191128215348210](/Users/hamster/Library/Application Support/typora-user-images/image-20191128215348210.png)
把ABC组合起来构成start,同时也是终结态。注意跟着这个表达式的结构画出NFA和DFA
子集构造法
这个MOOC讲的比较模糊,建议结合课堂例题再看一下,感觉期末肯定会考
DFA的每个状态都是一个由 NFA中的状态构成的集合,即 NFA状态集合的一个子集:
计算 ε-closure 空闭包函数
识别单词的DFA
过程总结:先写出正则定义,再写出NFA,最后把NFA转化成DFA
识别标识符的 DFA
下面图中识别标识符的 DFA,第一部分识别字母和下划线,第二部分识别字母和下换线和数字组成的串。
因为这个 NFA 就是 DFA,因此不需要进行转换。
识别无符号数的 DFA
第一部分是长度大于等于1的数字串,第二部分是可选的小数部分(两个子表达式进行或运算得到的),第三部分是可选的指数部分(两个子表达式进行或运算得到的)。
再将 NFA 转换成 DFA,如下图所示:
自己推导一遍
识别各进制无符号整数的 DFA
识别注释的 DFA
识别 token 的 DFA
识别关键字时把IDN与关键字表对照查询
词法分析阶段的错误处理
词法分析阶段可检测错误的类型
单词拼写错误
例:int i=0x3G,float j=1.05e
非法字符
例:~@
词法错误检测
如果当前状态与当前输入符号在转换表对应项中的信息为空,而当前状态又不是终止状态,则调用错误处理程序。
错误处理程序
根据最长子串匹配原则,查找已扫描字符串中最后一个对应于某终态的字符
- 如果找到了,将该字符与其前面的字符识别成一个单词。然后将输入指针退回到该字符,扫描器重新回到初始状态,继续识别下一个单词
- 如果没找到,则确定出错,采用错误恢复策略。
错误恢复策略
最简单的错误恢复策略:“恐慌模式”恢复
从剩余的输入中不断删除字符,直到词法分析器能够在剩余输入的开头发现一个正确的字符为止。
语法分析
语法分析的任务是根据给定的文法,识别数据中的各类短语,并构造语法分析树。如果输入串的各个单词恰好自左至右地站在分析树的各个节点上,则该词串就是该语言的一个句子。
构造的方法主要分为2类,包括自顶向下分析和自底向上分析。
自顶向下分析概述
自顶向下的分析是指从分析树的顶部(根节点)向底部(叶节点)方向构造分析树,可以看做是从文法开始符号S推导出单词串w的过程,例如:
输入为id+(id+id),可以理解为通过语法分析树可以最终得到id+(id+id),即将叶子节点连接起来就可以构成id+(id+id)。
每次推导中都要作两个选择:
- 替换哪一个非终结符
- 用哪个候选式来替换非终结符
最左推导和最右推导
最左推导
在最左推导中,从文法的开始符号起,总是选择每个句型的最左非终结符进行替换,得到的句型称 为当前文法的最左句型(left-sentential form)
最右推导与最左规约
在最右推导中,总是选择每个句型的最右非终结符进行替换
在自底向上的分析中,总是采用最左归约的方式,因此把最左归约称为规范归约,而最右推导相应地称为规范推导
从右边开始推导比较规范
自底向上:最左规约
自顶向下:最左推导
最左推导和最右推导具有唯一性。
自顶向下的语法分析采用最左推导方式
自顶向下的语法分析采用最左推 导方式,总是选择每个句型的最左非终结符进行替换 ,根据输入流中的下一个终结符,选择最左非终结符的一个候选式。
试着写一下下面的题,用最左推导
当非终结符的选择不止一种的时候,叶子节点是什么要参考当前的输入指针是什么。当前指针指向的符号已经使用则进行后移。如指针指向+号表示:此次要选择以+号开头的非终结符。
自顶向下语法分析的通用形式
递归下降分析(Recursive-Descent Parsing),由一组过程组成,每个过程对应一个非终结符 。
从文法开始符号S对应的过程开始,其中递归调用文法中其它非终结符对应 的过程。如果S对应的过程体恰好扫描了整个输入串,则成功完成语法分析。
void A( ) { |
但可能需要回溯,从而导致效率比较低。需要回溯的分析器叫做不确定的分析器。
预测分析(Predictive Parsing)
预测分析是递归下降分析技术的一个特例,通过在输入中向前看固定个数(通常是一个)符号来选 择正确的A-产生式。
可以对某些文法构造出向前看k个输入符号的预测分析器,该类文法有时也称为LL(k) 文法类
预测分析不需要回溯,是一种确定的自顶向下分析方法
。
文法转化
并不是所有的文法都适合自顶向下的分析。
同一非终结符的多个候选式存在共同前缀,将导致回溯现象,例如:
问题1:此时就会在a的时候产生歧义。
问题2:左递归文法会使递归下降分析器陷入无限循环
含有A→Aα形式产生式的文法称为是直接左递归的 (immediate left recursive)
如果一个文法中有一个非终结符 A 使得对某个串α存 在一个推导A⇒+AαA\Rightarrow ^+AαA⇒
+
Aα ,那么这个文法就是左递归的。
经过两步或两步以上推导产生的左递归称为是间接左递归的。
LL(1)文法
语法制导翻译
语法制导翻译概述
将语义分析和中间代码生成统称为语义翻译,而语义翻译和语法分析统称为语法制导翻译,边分析语法,边完成制导翻译的过程
语法制导定义(SDD)
综合属性和继承属性
SDD的求值顺序
S-属性定义与L-属性定义
语法制导翻译方案
语法制导翻译方案(SDT)是在产生式右部中嵌入了程序片段(称为语义动作)的CFG。SDT可以看作是SDD的具体实施方案,本节主要关注如何使用SDT来实现两类重要的SDD, 因为在这两种情况下,SDT可在语法分析过程中实现 :
- 基本文法可以使用LR分析技术,且SDD是S属性的
- 基本文法可以使用LL分析技术,且SDD是L属性的
S-SDD转换为SDT
将一个S-SDD转换为SDT的方法:将每个语义动作 都放在产生式的最后。例如:
L-SDD转换为SDT
在非递归的预测分析过程中进行语义翻译
在递归的预测分析过程中进行语义翻译
在LR分析过程中进行语义翻译
中间代码的生成
课件PPT
中间语言
?这个四元表达式之前有MOOC讲解的知识点,后期记得补上
- 中间语言(复杂性界于源语言和目标语言之间) 的好处:
中间代码也叫中间语言(Intermediate code /language)是:源程序的一种内部表示,不依赖目标机的结构,复杂性介于源语言和机器语言之间。
- 常用中间语言,重点讲了三地址代码的四元式,==给一个语句要求写出四元式==
四元式
例题
- 三地址语句的种类,
?有些种类的不是到怎么写四元式
说明语句的翻译
?PPT没有内容,要掌握吗
赋值语句的翻译
赋值语句的基本文法
赋值语句翻译的主要任务是生成对表达式求值的三地址码
例题,根据表达式写出三地址码和四元式 ->
赋值语句对应的语法制导翻译SDT
赋值语句翻译的主要任务是生成对表达式求值的三地址码,因此为基本文法中的非终结符S和E分别设置了综合属性code,用来表示这两个文法符号对应的三地址码,另外E还有一个综合属性addr,用来表示表达式的值的存放地址
几个其中函数:
- lookup(name):查询符号表 返回name对应的记录;
- gen(code):生成三地址指令code;
- newtemp( ):生成一个新的临时变量t, 返回t的地址addr。
布尔表达式的翻译
控制语句的翻译
过程调用的处理
dfsagg
控制流语句及STD
先分析控制流语句的代码结构,定义文法符号的属性
其中包含的继承属性为:
S.next:是一个地址,该地址中存放了紧跟在S代码之后的指令 (S的后继指令)的标号
B.true:是一个地址,该地址中 存放了当B为真时控制流转向的 指令的标号
B.false:是一个地址,该地址 中存放了当B为假时控制流转向 的指令的标号
用指令的标号标识一条三地址指令
然后编写控制流语句的基本文法及STD
newlabel( ): 生成一个地址;
label(L): 将下一条 三地址指令的标号赋给L
1.P->S
课堂笔记
11.07
1.概念:算符(要相邻),素短语,算符优先关系
2.例题:书本P89
3.判断是否为算法优先文法,算符之间只能有一个关系
4.算符优先关系表
5.FIRSTVT和LASTVT,可以用来构造优先关系表
6.PPT41页例题和书本P91笔记
7.根据PPT41写出PPT44例题
8.PPT46的素短语和最左素短语
9.PPT47的规约过程
10.PPT51页例题
11.规约准确率高,算符优先法速度快
11.14
一、复习
1.规约:找句柄
2.算符优先文法:找最左素短语,i直接返回为最顶端的N,有算符才能规约,否则都是占位符,一定要包含算符。速度快但有时不准确,前提是符合算符优先文法才能用。很多都不适用
3.另一种:LR分析法
二、LR分析法:
1.从左到右扫描符号串,用最左规约,最右推导。能解决大部分语法规则,但二义文法,需要处理才能用。
2.LR(1)分析表,算FIRST和FELLOW,有多个候选式。而LR分析法我们用的是LR分析法,有很多能自动构造LR分析表。
3.课本P101例题,状态栈和符号栈,遇到s5:状态转为5,移入i,指针后移;r2:用(2)表达式进行规约右侧符号,消除此时状态,写入规约后符号,查询Goto表更新状态。栈顶的状态显示了分析的历史信息/进度。
4.根据有限状态自动机DFA,LR(0)item组成的集合
11.28
一、书本125,二义文法不能用LR分析法,图5.11和表5.7的第7行,对于二义文法进行人为选择优先级以规避冲突
二、进入第六章
- 综合属性、继承属性
- 终结符只有综合属性,比如digit
- S-属性定义属于L-属性定义
- L-属性需要综合属性时可能尚未计算结束,需要多扫描几遍才行,参考其他文件的从左到右计算
- PPT第22页例题,10进制实数
三、进入第7章
语义分析和中间代码的生成
本章目录,这些翻译在参考资料里好像没有
常见的中间语言,用的比较多的是三地址代码,四元式表达
本书涉及的三地址语句类型
布尔表达式和控制流语句,两种方法,一种按部就班,一种进行优化,一真一假的话短路掉部分计算
12.05
- 条件为真/假的时候,出口不一样,还要加上一个GOTO语句
- 如果边分析边生成的话,会比较难一遍扫描完成-》回填机制
- 引入ture\false\next三个属性
循环体也要有一个GOTO,为S增加begin属性
顺序执行,保留S.next的原因,因为S可能是条件/循环语句等,
布尔表达式两种翻译方式:
- 两遍扫描:为给定的输入串构造一棵语法树;对语法树进行深度优先遍历,进行语义规则中规定的翻译。
- 一遍扫描
分析语法都用LR分析法
一遍扫描的困难:
如If-then-else,在这个语句中,if会有两个jump(j, , ,跳转地址),插入GOTO语句,用M来用链条连起来,用回填语句M整个扫描完后要规约为一个S;但是扫描到if的时候还不知道入口的跳转地址,定义一个M,示例PPT36页,
emit是要增加一条中间代码
merge合成一个
backpatch是用于回填,先用指针指向
老师上课重点讲了这两个的作用,为了一遍扫描生成中间码
M的作用
:用M.quad回填E.turelist,用于记录S1和S2的入口地址,两个backpatch回填
N的作用
:对于if-then,完成之后要跳出这个条件语句,强制跳转语句
M与N都是空字
这个语句知道E的真和假,E的假不用执行了,真需要回填
总共三个跳转:只回填真语句的回填,N的回填,假的回填不用
把不知道的地方放到S的属性,S.nextlist=merge(),不知道的先merge到S.nextlist上去,指针附在nextlist后面,什么时候回填还不知道,需要回填的时候在进行回填
边扫描边生成代码:不知道的全部空着,然后用链条把这个放到上一级S的属性,等到知道入口在哪里再进行回填
当E1为假才去看E2,用M记录入口地址
一条中间代码都没有产生。由于不知道E1和E2的真出口,所以要用第2句把E1和E2链接到E上,AorB都是假的话需要一个假出口,那么跳出,所以E2E2的假出口也是E的假出口,这时整个AorB都是假。
merge是链表的合并
AND
E1的真出口是E2的入口地址
E2的真出口是E的真出口
E2、E1的假出口都是E的假出口
NOT ()
OR AND NOT ()都没有中间代码的生成,只有涉及到id的时候才产生中间代码。
中间代码:如果满足relop.op操作,操作数满足该关系,就往这个真出口出去,为假的话就强制执行假出口;所以这里总共有2个出口。
中间代码也有需要回填,所以要把真假出口挂到E的链条上
如果回填了的话nextquad会发生改变
举例
:E(C)的假出口需要往105回填为106,不知道怎么回填的出口还是挂到上一级。8条代码里3个地址确定,不确定的其他传到E上
两种方法,以上一种(画图、生成中间代码),以下方法
:PPT28页
优先级:先做and后做or,图中标出了两个真出口和两个假出口
控制语句的翻译
while要跳回头部判断,S1.begin
对E的真出口进行回填,假出口要挂在S上,S.nexilist也要回填!!!
例题,包含了布尔、控制、赋值语句
a<b的假出口为S.next,不要随意写跳到107
12.12
控制语句的翻译
begin不见了,用M1代替
[????]
S->begin L end
S->A 【S定义为一个语句】
{S.nextList:=makelist()}
A如果是一个赋值语句,因为要用S来表示它,A要规约为S,
为了和其他while等保持一致,留一个接口,但是直接给个空就行
L->S
{L.nextlist:=S.nextlist}
向前转移和向后转移
L已经定义为了变量而不是标号或者还没定义,报错
L:
case语句,也有叫做switch
两种翻译法生成语义的方法是一样的,但是第一种比较方便优化
把L都标出来,最后统一做判断
过程调用的处理
我们只讲转地址,不讨论传参数
过程调用的翻译子程序
??? 没听懂
把MOOC看了
优化
符号表和内存,跳过
==先讲了循环优化啥的再讲的基本块==
单一入口没有入口和goto跳转,顺序执行的
循环是优化的重点
12.19
局部优化
什么是基本块、怎么划分、
划分基本块,课堂详讲的练习,讲了2次
在一个基本块内进行优化
基本块用DAG表示
叶子结点是标识符或常数,然后添加到上级结点(附加操作符)
常见DAG:
基本块的DAG构造算法:举例课本P284,看!!!
合并已知量:如果来两个都是常数,则删除,参考步骤b,还有其他规则。。。。建议看MOOC
DAG图的优化
:
)
最后简化为4个运算,公共变量用S代替
目标代码生成——最后一章
四元式有一个优化,最后代码也有一个优化
==第一个操作数==
了解一个简单的代码生成器
附加在四元式的待用/活跃信息表
每一个变量是否会在后面被引用
比如第一个表:T=A-B,T在第3个被引用,A在第2个引用,B在右操作数被引用
代码生成算法
作业习题
作业,P306 3(B2)
考试
选A卷
判断题10分,
目标码
前3重中之重
做过的题一定会做
词法分析
优化放在B卷考
词法、语法、中间代码。 伪代码,目标有一点
概念性的东西
2*5,2题,每题5分 判断
3*8 选
22*3 大题
词法分析 语法分析(分析器要会) 中间码生成(子程序提供,要用过程)蓝色基本上可以忽略