编译原理小结——字符和字符串之间的游戏
来源:互联网 发布:jabra elite sport软件 编辑:程序博客网 时间:2024/06/07 00:07
前记:第一次使用LaTeX编辑公式,这个东西不错~让我意识到了转义符号’\’的强大威力!
吐槽:本人使用清华大学出版社的《编译原理》第二版,这本书真TM抽象,各种乱七八糟的形式化定义一个接着一个,看得我吐血了。考前突击,花了四天时间硬是把这本书的核心部分啃下来了,写下了8千字的”小”结~
《编译原理》(清华出版社)教材链接
1 词法分析 lexical analysis
词法分析是编译的第一个阶段:主要是从左至右逐个字符地对字符流的源程序(比如.c 和 .cpp)进行扫描,产生一个个单词(token)序列。简而言之:读入源程序,输出单词符号。单词符号是一个程序设计语言的基本语法符号,一般分为以下5种:
- 关键词:for, while, if, else, double, int
- 标识符:自己声明的变量、类/对象
- 常 数 :”hello, world”, 12345
- 运算符: >, =, ==, +
- 界 符 : 逗号,分号,花括号
按理说,词法也是语法的一部分。不过编译器把词法分析从语法分析中独立出来,是因为有些好处:
- 使整个编译程序的结构更加简洁、清晰、条理化
- 编译程序的效率会改进
- 增强程序的可移植性
词法分析做的一些细节工作:滤掉程序中的注释和空白(空白即空格、换行符、制表符) ;记录读入字符行的行号(比如编译器会提示error在第几行); 完成预处理,比如C/C++的#defile
正规集有两种表示工具:正规文法(3型文法)和正则式,正规集是正规文法所定义的语言和正规式所表示的集合。两种表示方法是等价的,可以互相转换。正则式即正则表达式,它由递归定义:首先设字母表是
ε 和∅ 都是∑ 上的正规式,它们表示的正规集分别为{ε} 和∅ - 任何 a
∈∑ ,a 是∑ 上的一个正规式,它表示的正规集为{a} - 假定
e1和e2 都是∑ 上的正规式,它们表示的正规集分别为L(e1 )和L(e2 ),那么(e1) , e1∣e2 , e1⋅e2 , e∗1 也都是正规式,它们表示的正规集分别为L(e1) , L(e1)⋃L(e2) , L(e1)L(e2) , (L(e1))∗ - 仅由有限次使用上述三步而定义的表达式才是
∑ 上的正规式,仅由这些正规式所表示的字集才是∑ 上的正规集。可以看出经过递归调用,正规集 L“越来越大”
有穷自动机是一种识别装置,可以准确的识别正规集。分为两类:NFA(Nondeterministic Finite Automata,不确定的有穷自动机) 和 DFA(Deterministic Finite Automata,确定的有穷自动机) ,它们都可以用五元组
NFA通过子集法可以转换到DFA,子集法的要点是掌握两种运算:闭包运算和弧转换运算。这里必考大题:正则式 –> NFA –> DFA,主意事项:1.确定化的时候不重复,不遗漏;2.假设NFA中的终态是Z,Z可能出现在DFA的多个状态中,那么这些出现过Z的状态都是终态,所以DFA中的终态可能会有多个;3.在“填写表格”(确定化)的时候,别忘记闭包了(虽然一些题目没有涉及
2 语法分析 grammar analysis
1 概念
一个程序设计语言是一个记号系统,它的完整定义应包括语法和语义两个方面。所谓语言的语法是指一组规则,用它可以形成和产生一个合适的程序。语法定义什么样的符号序列式合法的。
重要概念和定义:
- 字母表:元素的非空有穷集和。字母表中的元素称为符号,字母表又称符号表,一般用
∑ 表示 - 符号串:由字母表中的符号所组成的任何有穷序列。
- 符号串集合:若集合A中的一切元素都是某字母表上的符号串,则称A为该字母表上面的符号串集合
- 闭包:
∑∗ ,∑ 上的所有有穷串的集合 。∑∗=∑0⋃∑1⋃∑2...⋃∑n... - 正闭包:
∑+ ,在∑∗ 中去除∑0 即可 - 规则,又称产生式,形如
α→β 的有序对,读作“定义为” - 文法 G定义为四元组
(Vn,Vt,P,S) :Vn 是非终结符集;Vt 是终结符集;P是规则(α→β )的集合;S是开始符,是一个非终结符。Vn 和Vt 互斥,两者的并集等于文法G的字母表。S要求至少在一个规则中作为左部出现 - 推导:结合上面的两个定义,又设
γ,θ 分别是V∗ 中的任意字符串,满足v=γ α θ ,w=γ β θ ,则v (应用α→β )直接产生w ,w 是v 的直接推导,w 直接归约到v ,记作v⇒w 。如果v 经过“多次直接推导”产生w ,则v⇒+ w (这个’+’在符号上面,如果是’*’的话,这可能v=w ,即根本没有推导) - 开始符 S 经过文法G[S]的不断推导,产生了句型(一个句型 = 对应语法树的叶节点从左至右串在一起,包含
Vn );句型如果全部由Vt 里的元素组成,则变成了句子;所有的句子便成为了语言L(G) 。文法描述的语言,是该文法一切句子的集合。
文法分为四种:0型文法、1型文法(上下文有关 context sensitive)、2型文法(上下文无关 context free)、3型文法(正规文法)。4个文法的差别在于对产生式施加的限制不同,具体的说,是限制逐级上升。0型文法的能力相当于图灵机,2型文法的能力相当于下推自动机。
其中上下文无关文法有足够的能力描述现今程序设计语言的语法结构,特点是:其产生式的左部有且仅有一个非终结符,这是课本的缺省文法。语法树(推导树)可以直观的描述上下文无关文法的句型推导过程。如果在任何一步
二义:顾名思义,一个句子有两种意义。如果说一个文法存在某一个句子对应两颗不同的语法树,则说此文法是二义的。本人总结:依据课本上的例子,消除二义性可以通过“增加新字符”解决。本人脑补:程序语言之所以都是英文的:一方面是计算机诞生于美国,而美国的母语是英语;另一方面是因为英语的二义性很低,相比较之下,中文的二义性比较高,导致中文很难成为一门程序语言的载体。
短语:设G是文法,S是开始符号,
2 重难点
首先词法分析输入字符流,得到单词序列(本质上也是字符流,只不过进行了处理)。然后句型分析是输入单词序列,识别它们在语法上是否正确,这是语法分析的核心部分。课本上一律采用 从左至右的分析方法。句型分析分成两大类: 自上向下、自下向上。考试中,这里肯定考大题。
- 自上向下:从文法的开始符号出发,反复使用各种产生式,寻找“匹配”于输入符号串的推导。
- 自下向上:从输入的符号串开始,逐步进行归约,直到归约到文法的开始符号。
1 自上向下
如果一个文法满足LL(1)文法,那么其自上向下推导一定是确定的。自上向下推导如果不是确定的,则只能用带回溯的分析方法(效率低,代价高)。为了弄清楚LL(1)文法,首先要引出两个重要概念。设
- FIRST(A)=
{a∣A⇒∗ aB,a∈Vt,A,B∈V∗} 。如果A⇒∗ ε ,则ε∈ FIRST(A) - FOLLOW(A)=
{a∣S⇒∗ μAβ,a∈Vt,a∈ FIRST(β),μ∈V∗t,β∈V+} , 如果β⇒∗ ε ,则#∈ FOLLOW(A) 。’#’ 是输入串的结束符
定义有一点复杂,总结一下核心。相同点是:FIRST,FOLLOW集的元素一定
本人做题教训:我开始以为课本上的
再引入第三个概念:SELECT(A)。
定义有一点复杂,概括一下。关键点在于
LL(1)的特点是:无二义,不含左递归。每输入一个字符,可以确定使用哪一个产生式。假设输入a,现在需要推导A,那么a属于哪个SELECT(“A的某产生式”)集,就使用”A的某产生式”推导A。第一个L,是指从左至右扫描字符串;第二个L,是指分析过程中用最左推导,1表明向右看一个输入符号便可以决定推导使用那一个产生式(如果仅仅需要用FIRST集,向右看0个;如果需要用到FOLLOW集,才会向右看1个)。非LL(1)到LL(1)文法的转换方法有提取左公因子和消除左递归(包括直接和间接左递归)。不过,上面两种方法,并不能确保转换成LL(1)。
消除直接左递归的方法:假设有产生式1:
2 自下而上
自上而下的分析算法能力强,构造复杂,最常用和最有效。其核心是移进和归约,需要用到stack。大体思想是:对输入符号串进行从左至右扫描,将输入字符逐个移进一个栈中,边移进边分析,一旦栈顶符号串形成某个句型的”句柄”(可归约串)时,而且对应”某产生式”的”右部”,就用”某产生式”的”左部”代替该”句柄”,这称为归约。重复上述过程,直到归约到栈中只剩下开始符时,则分析成功。自下而上分为两类:算符优先和LR分析,大题必考哟。自下向上分析的关键问题是如何确定句柄(可归约串),当栈顶已经形成了某句型的句柄时,就意味着可以归约了。
1 算符优先
首先提到 简单算符优先:它把非终结符和终结符的优先关系都求出来了,是一种规范归约。但是效率低,没多大实用价值,老师的PPT都没讲,肯定不考。算符优先分析方法:只考虑算符(广义为终结符)之间的优先关系。首先来看看三种优先关系
算符文法:设有一文法G,如果G中没有形如A
FIRSTVT(B)=
LASTVT(B) =
做题总结:在求FIRSTVT(A)的时候,在产生式中找到左部是A的产生式开始推导;在推导时候,只关心最左边的符号,如果是终结符就结束该分支的推导,如果是非终结符就继续推导这个非终结符。同理,求LASTVT集的时候,只关心最右边的那个符号。记住可以“重复利用”前面推导过的字符。
构造算符优先关系表:(大写字母代表非终结符,小写字母代表终结符)
≑ 寻找诸如A→⋯ ab⋯ 或 A→⋯ aBb⋯ 的产生式,那么a≑b - 求出所有非终结符的FIRSTVT集和LASTVT集
⋖ 寻找诸如A→⋯ aB⋯ 的产生式,记录所有的”aB”,在FIRSTVT(B)中找到所有对应的b,则a⋖b ,在表的左侧找到a,向右填写到表中⋗ 寻找诸如A→⋯ Bb⋯ 的产生式,记录所有的”Bb”,在LASTVT(B)中找到所有对应的a,则a⋗b ,在表的顶上找b,向下填写到表中- 有一些题目的产生式中没有’#’,但是最后的算符优先表中’#’不可缺少,记住补上
算符优先分析归约的关键,是如何找到最左素短语。归约的时候,非终结符是啥无所谓,可以把一个终结符变成任意一个“大写字母”。在栈的顶部的终结符a和当前输入的字符b之间比较有限关系,如果a
素短语:设有一个文法G,素短语满足如下两个条件的短语,他至少包含一个终结符,并且除了自己以外,不包含其他素短语(有点绕,就是说它是一个“最小单位”)。最左素短语是一个句型中,最左边的素短语。
2 LR分析法
LR分析法是一种规范归约,能根据当前分析栈中的符号串和向右顺序查看k个(k
最右推导是规范推导,最左归约是规范规约。在LR(0)归约的时候,有两个栈:状态栈和符号栈,有一个输入串,还有ACTION和GOTO。对于一个合法的句子,每次归约后得到的都是由“已归约部分”和”输入剩余部分”合起来构成文法的规范句型。每一次归约之前,规范句型的前部称为可归前缀(放在符号栈里面的就是这个东西)。”可归前缀的前缀”(可以包括可归前缀它自己)称为活前缀。活前缀更加直观的理解:规范句型的一个前缀,这种前缀不含句柄之后的任何符号。形式化定义如下:若
句柄:其定义前面已经给出了,然而句柄还有第二种理解方法。课本上有一句话:在规范规约的分析中,“可归约串”称作句柄。这和前面提到的“最左直接短语”的定义相差不小。最明显的差距在于,“最左直接短语”的定义方式决定了句柄只有一个(在一个句型中);而”可归约串“这种定义,暗示着句柄可以有很多个,一个文法有n个产生式,那么这n个产生式的右部可能都是句柄。LR分析方法是一种规范规约,那么这里的句柄就当作”可归约串“。课本上还有一句话:”可归约串“这个概念的不同定义,形成了不同的自下而上分析方法。这真是让人脑洞大开~
LC(A)=
LR(0)项目集规范族,构成了识别一个文法的活前缀的DFA的状态的全体。这里引入了“
又有了两种方法:一种是求出文法的所有项目,构造一个识别活前缀的NFA,再把NFA转换成DFA;另一种是绕开NFA,直接构造DFA。后者具体如下:
- 把第一个项目作为初态集的核,对核求CLOSURE(核),得到初态的项目集
- 对初态集或其它构造的项目集应用转换函数GO( I, X ) = CLOSURE( J ),求出新的状态的项目集
- 重复步骤2直到没有产生新的项目。
这里的CLOSURE( J )集和GO( I, X )集分别和有限自动机里面的闭包运算和MOVE运算异曲同工:
- 状态 J 的项目均在CLOSURE( J )中
- 若 A
→α⋅ Bβ 属于CLOSURE( J ),则每一形如B→⋅ γ 的项目也在CLOSURE( J )中 - 重复步骤2,直到不产生新的项目集
GO( I, X ) = CLOSURE( J ),其中 I 为包含某一项目集的状态。X
总共3种方法的出发点都是把LR分析法的归约过程,看成是识别文法规范句型的前缀的过程。只要分析到的当前状态(符号栈、状态栈)是活前缀的识别态,这说明已经识别过的部分是该文法的某一规范句型的一部分,也就是说已经分析过的部分是正确的。
LR(0)项目集规范族的项目类型有4种:移进项目A
LR(0)分析表:是一个二维数组,行标是状态号,列标是文法符号和’#’,ACTION表和GOTO表重叠。查看ACTION表:先看行,假设是状态 x,再看列,假设符号是 y,而且是终结符,那么ACTION[x][y]的值就是移进、归约、接受、出错(空白)中的一个;y 如果是非终结符,GOTO[x][y]的值就是转向的状态。注意 y 是终结符(小写)或者非终结符(大写)。构造LR(0)分析表,首先要构造其识别活前缀的自动机DFA。至于如何把DFA转换成对应的LR(0)分析表,做一道题练练手就懂了。当在试图构造的LR(0)分析表时,没有出现多重定义,那么这样的分析表为LR(0)分析表,能构造LR(0)分析表的文法称为LR(0)文法。
LR(0)题目小结: 1.首先需要扩展文法,增加一个产生式; 2.状态编号从0开始,按照项目的顺序,向下依次标号,状态
SLR(1)分析法
LR(0)分析方法能力很弱,很多实用的程序设计语言的文法不能满足LR(0)文法。SLR(1)基于容许LR(0)规范族中有冲突的项目集(状态),用向前查看一个符号的办法来处理冲突。因为只对有冲突的状态才向前查看一个符号,以确定做那种动作,所以是Simple的。
SLR(1)出现移进-归约冲突的时候,需要求出归约项目的左部的FOLLOW集,这些FOLLOW集如果互不相交(可能有多个归约项目)并且和移进项目的圆点右边的终结符不相交,则符合SLR(1)文法。此时更改一下LR(0)分析表,就形成了新的SLR(1)分析表。
3 语义分析 中间代码 优化 及其他
在语法分析之后,便是语义分析。语义分析有两个功能:一,检查语法结构的静态语义(语法规则的良形式条件),即检查语法是合法的程序是否真正有意义,是一种程序的约束的描述,分为类型规则和作用域规则;二,动态语义(程序单元执行的操作)处理,执行真正的”翻译”,即生成中间代码。
很多编译程序采用属性文法对语义处理工作进行比较规范和抽象的描述。一个属性文法包含一个上下文无关文法和一系列语义规则,这些语义规则附加在文法的产生式上面。属性文法是一个三元组,A=( G, V, F ),G 是上下文无关文法,V 是属性的有穷集,F 是关于属性的断言或谓词的有穷集,每个断言与文法中的某个产生式相联。属性分成两类:继承属性和综合属性。区分规律:观察产生式 A
中间代码是源程序的一种内部表示方法,其复杂性介于源程序和目标程序。主要形式有4种:逆波兰式,四元式,三元式,树。中间代码可以进行优化,其宗旨是获得更好性能的代码。它对程序进行各种等价变换,使得变换过后的代码更高效。含有优化功能的编译程序,其优化是指对生成的目标代码进行优化,而不是编译程序本身得到优化,因此提高目标代码的效率,而不是编译程序的效率。其中局部代码优化是指局限与基本块内的优化。基本块是指程序中一顺序执行的语句序列,其中只有一个入口语句和一个出口语句。常见的代码优化技术有:
- 删除多余运算。删除两个相同的表达式
- 循环不变代码外提。减少局部变量的重复产生
- 强度消弱。除法–>乘法,乘法–>加法, 加法–>位运算
- 变换循环控制条件。减少循环次数
- 合并已知量,变量传递。
- 删除无用赋值
编译程序是一种常用的系统软件,与具体的机器有关,与具体的语言有关。编译程序是一个语言的翻译程序,是一种把源语言书写的程序翻译成目标语言的程序。编译过程一般分为前段和后端,前段包括词法、语法、语义、和中间代码生成;后端包括中间代码优化和目标代码生成。
- 编译原理小结——字符和字符串之间的游戏
- 字符串和字符数组之间的转换
- 字符串和字符数组之间的转换
- 字符串和字符数组之间的转换
- 字符串和字符数组之间的转换
- 字符串和字符数组之间的转换
- 编译原理之求字符串和字符个数
- C的字符串和字符数组之间的关系
- C# 字符串和字符数组之间的转换
- 字符串对象和字节化字符数据之间的转换
- c语言中的字符数组和字符串之间的关系
- 关于c++中字符串和字符指针之间的转化
- C、C++中字符串和字符指针之间的转换
- 查找所字符串中开始字符和结束字符之间的所有字符
- C语言宽字符——字符集与字符编码和宽字符之间的关系
- 字符数组、字符串数组和字符串三者之间的转化
- 宽窄字符之间的转换——字符串处理(三)
- 字符、字符串、数组之间的转换
- PriorityQueue优先级队列
- benchmark
- coderforce 550A. Two Substrings
- SSL 之数字签名
- android如音符般的震动
- 编译原理小结——字符和字符串之间的游戏
- (转载)Deep Learning(深度学习)学习笔记整理系列之(四)
- MEMS传感器
- redhat的bugdb
- 静态库和动态库的区别
- dubbo学习笔记
- javascript之window对象
- (转载)Deep Learning(深度学习)学习笔记整理系列之(五)
- 杭电ACM1863(prim)