说明
- 只总结到了LR之前,后面没怎么学,但编译原理实在是太美了,以后会接着学
- 本篇文章是根据这个 哈工大网课 总结的,讲的很好,非常简练,一些难点需要反复观看
- 中南大学的录屏,讲的很好,非常细致,哈工大看不懂的可以看这个
- 算法实现理论课考试一般是不考的,但是对编程实现有帮助
🥰
第一章 基础知识
编译:将高级语言翻译成汇编语言或机器语言的过程
语句可以分为:声明语句和执行语句
输入:字符流
1.词法分析:从左向右逐行扫描源程序,确定单词类型,将识别出来的单词转换成token
token:<种别码,属性值>
token流
2.语法分析:从词法分析器输出的token序列中识别出各类短语,构造语法分析树。
语法分析树
3.语义分析:字符表(标识符的属性信息),语义检查(变量或过程未声明就使用或重复声明,操作符与操作数类型不匹配、数组下标不是整数、函数返回类型不对等等)
语法分析树
4.中间代码生成:表示形式:三地址指令(类似于汇编代码指令序列,每个指令最多有三个操作数);语法树
三地址指令的表示:四元式(op,y,z,x)、三元式、间接三元式
中间代码
5.代码优化:对中间代码进行改造
中间代码
6.目标代码生成:一其中个重要任务:为程序中使用的变量合理分配寄存器
目标代码
第二章文法
句型:既可以包含终结符,也可以包含非终结符,也可能是空串
句子:不包含非终结符的句型
语言L(G):由文法G的开始符号S推出的所有句子构成的集合
文法的分类:
0型
1型(上下文有关文法)
2型(上下文无关文法):被研究最多
3型(正则文法)
▲例子都是标识符的文法
作业:写出标识符的左线性文法
四种文法之间的关系
CFG分析树
短语:每一颗子树的边缘
直接短语(简单短语):高度为2的子树的边缘
句柄:需要最左规约的元素
二义性文法(Ambiguous Grammar)
可以为某个句子生成多颗语法树的文法,namely,有两个不同的最左(右)推导
没有算法能够判断一个2型文法是否无二义性
但能给出一组充分条件,满足条件的一定无二义性,但不满足不一定有。
正则表达式(正规表达式)
运算优先级:(),*,连接,|
正则表达式(a|b)(a|b)表示的语言:
L((a|b)(a|b)) = L(a|b)L(a|b) = {a,b}{a,b} = {aa,ab,bb,ba}
第三章词法分析
现态+输入 -> 输出
有穷自动机FA(Deterministic finite automata):确定的DFA ,非确定的NFA
M = ( S,Σ ,δ,s0,F )
S:有穷状态集
Σ:输入字母表,即输入符号集合。假设ε不是 Σ中的元素
δ:将S×Σ映射到S的转换函数。s∈S, a∈Σ, δ(s,a)表示从状态s出发,沿着标记为a的边所能到达的状态。
s0 :开始状态 (或初始状态),s0∈ S
F :接收状态(或终止状态)集合,F⊆ S
A没有任何输入也可以到状态B和终态C,所以ABC都算是终态和初态
带不带空的NFA都有相对应的DFA
NFA(不确定的)较直观,但DFA(确定的)实现起来比较简单
DFA的算法实现
输入:以文件结束符EOF结尾的字符串x。DFA的开始状态S0,接收状态集F,转换函数move
输出:如果D接收x,则回答yes,否则回答no
方法:将下述算法应用于输入串x
正则表达式转换成有穷自动机
直接转化成DFA比较难,一般先转换成NFA
根据NFA得出转换表,将状态集合作为一个新的状态,生成DFA
最小化DFA
输入输出完全一样的状态,可以合并为一个状态
子集构造法(subset construction)
输入:NFA
输出:DFA
方法:核心思想:循环遍历,不漏下任何一种情况,和手动做差不多
识别单词的DFA
错误处理 词法分析阶段可检测错误的类型:
- 单词拼写错误 例:int i = 0x3G; float j =1.05e;
- 非法字符 例:~ @
词法错误检测:
如果当前状态与当前输入符号在转换表对应项中的信息为空,而当前状态又不是终止状态,则调用错误处理程序
错误处理:
查找已扫描字符串中最后一个对应于某终态的字符
- 如果找到了,将该字符与其前面的字符识别成一个单词。然后将输入指针退回到该字符,扫描器重新回到初始状态,继续识别下一个单词
- 如果没找到,则确定出错,采用错误恢复策略
错误恢复策略:
最简单的错误恢复策略:“恐慌模式 (panic mode)”恢复:
从剩余的输入中不断删除字符,直到词法分析器能够在剩余输入的开头发现一个正确的字符为止
第四章语法分析-自顶向下
推导过程不是唯一的,但最左最右推导是唯一的
自顶向下的语法分析采用最左推导方式:总是选择每个句型的最左非终结符进行替换,根据输入流中的下一个终结符,选择最左非终结符的一个候选式
通用形式
递归下降分析 (Recursive-Descent Parsing)
由一组过程组成,每个过程对应一个非终结符
从文法开始符号S对应的过程开始,其中递归调用文法中其它非终结符对应的过程。如果S对应的过程体恰好扫描了整个输入串,则成功完成语法分析
当有多个候选式都符合条件时(同一非终结符的多个候选式存在共同前缀),任选其中一个之后若后面走不通,则需要回溯
预测分析 (Predictive Parsing)
预测分析是递归下降分析技术的一个特例,通过在输入中向前看固定个数(通常是一个)符号来选择正确的A-产生式。
可以对某些文法构造出向前看k个输入符号的预测分析器,该类文法有时也称为LL(k)文法类
预测分析不需要回溯,是一种确定的自顶向下分析方法
文法转换
左递归文法会使递归下降分析器陷入无限循环
含有A→Aα形式产生式的文法称为是直接左递归的 (immediate left recursive)
经过两步或两步以上推导产生的左递归称为是间接左递归的
消除间接左递归:代入,将间接转换为直接,再消除
消除左递归算法:
输入:不含循环推导(即形如A多步推导得到A的推导)和ε-产生式的文法G
输出:等价的无左递归文法
提取左公因子(Left Factoring )
算法:
LL(1)文法
S_文法
每个产生式的右部都以终结符开始,同一非终结符的各个候选式的首终结符都不同
S_文法不含ε产生式
可以保证预测分析的确定性,选出的候选式是唯一的。
非终结符的后继(终结)符号集:
产生式的可选集:
串首终结符集:
同一非终结符的各个产生式的可选集互不相交
A → α | β 满足下面的条件:
- 如果α和β都不能推出ε,FIRST (α)∩FIRST (β) =Φ
因为如果相同,就会有多个候选式都符合条件(select有交集),需要回溯 - α和β至多有一个能推导出ε :
如果 β =>* ε,则FIRST (α)∩FOLLOW(A) =Φ;
如果 α =>* ε,则FIRST (β)∩FOLLOW(A) =Φ;
因为当其中一个可以推出空的时候,候选式选择的时候不只α和β的first集,也可以是跟在A后面的终结符,会有多个候选式都符合条件(select有交集),需要回溯
FIRST集&FOLLOW集的计算
计算单个符号的FIRST集:
不断应用下列规则,直到没有新的终结符或ε可以被加入到任何FIRST集合中为止。
- 如果X是一个终结符,那么FIRST ( X ) = { X }
- 如果X不是一个终结符,X→Y1…Yk∈P (k≥1), 把FIRST(Y1)元素加到FIRST ( X )里,如果ε在FIRST(Y1)里,那么把FIRST(Y2)元素加到FIRST ( X )里,如果如果ε也在FIRST(Y2)里,那么再把FIRST(Y3)元素加到FIRST ( X )里,以此类推 但是ε是不加入的,只有Y1…Yk全部都可以推出ε,再把ε加入。
- 如果 X→ε∈P,那么将ε加入到FIRST( X )中
计算完单个符号的FIRST集就可以计算一个符号串的FIRST集了:
Step1. 向FIRST(X1X2…Xn)加入FIRST(X1)中所有的非ε符号
Step2. 如果ε在FIRST(X1)中,再加入FIRST(X2)中的所有非ε符号;
如果ε在FIRST(X1)和FIRST(X2)中,再加入FIRST(X3)中的所有非ε符号,以此类推。
Step3. 最后,如果对所有的i,ε都在FIRST(Xi)中,那么将ε加入到FIRST(X1X2…Xn) 中
计算非终结符的FOLLOW集:
图解:
- 把结束符$/#放到FOLLOW(E)里
- FIRST(E’)(except ε)放到FOLLOW(T)里
- E’可以推出ε,所以把FOLLOW(E)放到FOLLOW(T)里
- E’可以推出ε,所以把FOLLOW(E’)放到FOLLOW(T)里
- FIRST(T’)(except ε)放到FOLLOW(F)里
- T’可以推出ε,所以把FOLLOW(T)放到FOLLOW(F)里
- T’可以推出ε,所以把FOLLOW(T’)放到FOLLOW(F)里
- 把 ) 放到FOLLOW(E)里
- 最右非终结符,所以把FOLLOW(E)放到FOLLOW(E’)里,FOLLOW(T)放到FOLLOW(T’)里
- 循环上述步骤,直至FOLLOW集不再变化 算法: 不断应用下列规则,直到没有新的终结符可以被加入到任何FOLLOW集合中为止。
- 将(书上是#)放入FOLLOW( S )中,其中S是开始符号,是输入右端的结束标记。
- 如果存在一个产生式A→αBβ,那么FIRST ( β )中除ε 之外的所有符号都在FOLLOW( B )中如果存在一个产生式A→αB,或存在产生式A→αBβ且FIRST ( β ) 包含ε,那么 FOLLOW( A )中的所有符号都在FOLLOW( B )中。
产生式的可选集SELECT集:
- 如果产生式的右部第一个字符为终结符,那该产生式的SELECT集就是该终结符
- 如果产生式的右部第一个字符为终结符为非终结符,SELECT集是该非终结符的FIRST集中的终结符
- 若为空,则该产生式的SELECT集就是该产生式左部的FOLLOW集
根据SELECT集经简单变换可以得到预测分析表:
递归的预测分析法
构造该文法的递归下降分析器的主程序DESCENT:
先求SELECT,除了4、7都很好求,根据依赖关系求出4、7
根据SELECT集就可以得到各个非终结符的过程:
非递归预测分析法
非递归的预测分析不需要为每个非终结符编写递归下降过程,而是根据预测分析表构造一个自动机,也叫表驱动的预测分析。
算法:
二者比较:
实现步骤
- 构造文法
- 改造文化:消除二义性、左递归、回溯
- 求每个变量的FIRST集,每个非终结符的FOLLOW集,从而求得每个产生式的SELECT集
- 检查是否为LL(1)文法:相同左部产生式的SELECT无交集
- 对于递归的预测分析:根据预测分析表为每一个非终结符编写一个过程;对于非递归的预测分析:实现表驱动的预测分析算法
错误处理
两种情况下可以检测到错误:
- 栈顶的终结符和当前输入符号不匹配
- 栈顶非终结符与当前输入符号在预测分析表对应项中的信息为空
错误恢复:恐慌模式:
- 忽略输入中的一些符号,直到输入中出现由设计者选定的同步词法单元(synchronizing token)集合中的某个词法单元。其效果依赖于同步集合的选取。集合的选取应该使得语法分析器能从实际遇到的错误中快速恢复。例如可以把FOLLOW(A)中的所有终结符放入非终结符A的同步记号集合。
- 如果终结符在栈顶而不能匹配,一个简单的办法就是弹出此终结符。
第五章语法分析-自底向上
通用形式
对输入串一次从左到右扫描过程中,将输入符号移入栈顶,知道栈顶的一个或多个符号可以进行规约 直到检测出语法错误。或栈中包含开始符号且输入缓冲区为空(这时语法分析成功完成)
LR分析法
LR文法是最大的,可以构造出相应移入-规约语法分析器的文法类。
L:对输入token流进行从左到右的扫描
R:反向构造出一个最右推导序列
LR(k)分析:需要向前查看k个输入符号的LR分析。
K=0,k=1这两种情况具有实践意义
省略k时,表示k=1
基本原理:
自底向上分析的关键:如何正确识别句柄
句柄是逐步形成的,用状态表示句柄识别的进展程度
LR分析器会基于状态来构造自动机进行句柄的识别
若把前面通过构造项目集规范族得到的DFA的每个状态都看作终结态,则该DFA是识别活前缀的DFA
DFA+栈=PDA
知识点
-
语法分析时必须先消除文法中的左递归 错。自底向上进行分析不需要消除左递归和左公共因子,自顶向下进行分析则需。
-
LR 分析法在自左至右扫描输入串时就能发现错误,但不能准确地指出出错地点。 对?
-
两个正规集相等的必要条件是他们对应的正规式等价。 错。正规集是正规式能表示的集合
-
词法分析器的输出结果是 单词的种别编号和自身值
-
如果文法 G 是无二义的,则它的任何句子α 最左推导和最右推导对应的语法树必定相同
-
四元式之间的联系是通过__临时变量_实现的。
-
计算机执行用高级语言编写的程序主要有两种途径:解释__和__编译。
-
扫描器是__词法分析器___,它接受输入的__源程序___,对源程序进行___词法分析__并识别出一个个单词符号,其输出结果是单词符号,供语法分析器使用。
-
自上而下分析法采用___移进__、归约、错误处理、___接受__等四种操作。
-
LR分析器包括总控程序、分析表、分析栈(状态栈,文法符号栈)
-
语义分析的基本功能包括: 确定类型、类型检查、语义处理和某些静态语义检查。
-
采用三元式实现三地址代码时,不利于对中间代码进行优化。
-
一个优先表一定存在相应的优先函数。错?
-
目标代码生成时,应考虑如何充分利用计算机的寄存器的问题。?
-
一个 LL(1)文法一定是无二义的。
-
正规文法产生的语言都可以用上下文无关文法来描述
-
编译过程可分为 ( 词法分析) ,(语法分析),(语义分析与中间代码生成 ),(优化)和(目标 代码生成 )五个阶段。
-
从功能上说,程序语言的语句大体可分为( 执行性 )语句和(说明性 )语句两大类。
-
符号表中的信息栏中登记了每个名字的有关的性质,如(类型、种属、所占单元大小、地址)等等。
-
根据优化所涉及的程序范围,可将优化分成为(局部优化),(循环优化),(全局优化)三个级别。
-
规范推导是最右推导,每次都替换最右边的非终结符
-
句柄:一个句型的最左直接短语
-
语法制导翻译:在语法分析过程中,根据每个产生式所对应的语义程序进行翻译的方法
-
素短语:至少含有一个终结符,并且,除它自身外不再含任何更小的素短语。
-
前端:包括词法分析、语法分析、语义分析、中间代码生成阶段以及相关出错处理和符号表管理
-
后端:依赖于目标机,包括目标代码生成以及相关出错处理和符号表操作
-
分前后端的优点:某一个编译程序的前端加上相应的后端则可以为不同的机器构成同一个源语言的编译程序;不同语言编译的前端生成同一中中间语言,再使用同一个机器的后端则可以为同一个机器生成几个语言的编译程序
-
遍:对源程序或其等价的中间程序从头到尾扫描并完成规定任务的过程
-
解释程序:不需要在运行之前把源程序翻译成目标代码,也可以运行并生成结果。
-
和编译程序的区别:编译型代表:C&C++,C#,Java,解释型代表:html,javascript。区别有很多,说说常见的几个: (1)编译型语言的源代码有错误编译不通过,无法生成可执行代码,更无法执行程序;解释型语言只有执行时才会判断是否出错,即使一句出错,也可以继续执行下一句。
(2)编译型语言都为强类型,即必须说明数据的类型,如int a;解释型语言多为弱类型,如js中var a,a可以为字符串也可以为整型。
(3)编译型语言执行效率上大大优于解释型,主要因为编译器在编译过程中会根据不同平台自动优化目标代码,且特点为1次编译,N次运行,另外强类型的程序安全性高;解释型语言无上述过程,逐语句翻译造成执行效率低下,每次执行都会重复解释一遍,并且安全性低。
(4)编译型程序适合对通用性,重复性,高效性有要求的系统,如开发操作系统;相比解释型语言更具灵活性,如开发网站前台页。
(5)编译程序编译时间较长,运行速度较快
大题
-
写一个文法 G, 使其语言为不以 0开头的偶数集: S -> ABC|C A -> 1|2|3|4|5|6|7|8|9 B -> DB|ε C -> 0|2|4|6|8 D -> 0|A
-
已知文法 G(S)及相应翻译方案 S→aAb {print “1”}
S→a {print “2”}
A→AS {print “3”}
A→c {print “4”}
输入 acab, 输出是什么?4231
写出LR自底向上分析过程 -
已知文法 G(S) S→bAb
A→(B | a
B→Aa)
写出句子 b(aa)b 的规范归约过程
-
文法 G(S) S→dAB
A→aA| a
B→Bb| ε
描述的语言是什么?
L(G)={danbm |n>0, m≥0} (n是次方)(d连接上n个a和n个b) -
已知文法 G(S) S→BA
A→BS| d
B→aA| bS | c
的预测分析表如下
给出句子 adccd 的分析过程。 -
目标代码有哪几种形式?生成目标代码时通常应考虑哪几个问题?
目标代码通常采用三种形式:机器语言,汇编语言,待装配机器语言模块。
应着重考虑的问题:
(1)如何使生成的目标代码较短;
(2)如何充分利用寄存器,以减少访问内存次数;
(3)如何充分利用指令系统的特点。 -
一个文法 G 别是 LL(1)文法的充要条件是: 相同左部的产生式的SELECT集无交集
(1) FIRST(α) ∩FIRST(β)=Ф
(2) 如果 β=* >ε, FIRST(α) ∩FOLLOW(A)= Ф -
已知文法 G[S] S→S*aF | aF | *aF
F→+aF | +a
消除文法左递归和提公共左因子。
S→aFA | aFA
A→aFA | ε
F→+aB
B→F | ε -
符号表的作用是什么?符号表查找和整理技术有哪几种?
作用:登记源程序中出现的各种名字及其信息,以及了解各阶段的进展状况。
主要技术:线性表,二叉树,杂凑技术。
名字+属性
编译过程中,每当扫描器(词法分析器)识别出一个名字后,编译程序就查阅符号表,看其是否在符号表中。
如果它是一个新名字就将它填进表里
选择题
1.如果文法G是无二义的,则它的任何句子α( )。
- A.最左推导和最右推导对应的语法树必定相同⭐
- B.最左推导和最右推导对应的语法树可能不同
- C.最左推导和最右推导必定相同
- D.可能存在两个不同的最左推导,但它们对应的语法树相同
2.( )文法不是LL(1)的。
- A.递归
- B.右递归
- C. 2型
- D.含有公共左因子的⭐
3.一个文法G,若( ),则称它是LL(1)文法。
- A. G中不含左递归
- B. G无二义性
- C. G的LL(1)分析表中不含多重定义的条目⭐
- D. G中产生式不含左公因子