封面图片选自:Photo by Dominik Van Opdenbosch on Unsplash 文章部分图片节选自国科大编译原理课件
- 注意:本文含有特殊符号 ε ∅ ∑ ∈ α ⇒
引论
编译程序
- 编译程序:把某一种高级语言程序等价地转换成另一种低级语言程序(如汇编语言或机器语言程序)的程序
编译过程
- 词法分析:输入源程序,对构成源程序的字符串进行扫描和分解,识别出单词符号
- 语法分析:在词法分析的基础上,根据语法规则把单词符号串分解成各类语法单位(语法范畴)
- 中间代码生成:对各类语法单位按语言的语义进行初步翻译(三元式或四元式)
- 优化
- 目标代码产生:把中间代码变换成特定机器上的目标代码
- 汇编指令代码: 需要进行汇编
- 绝对指令代码: 可直接运行
- 可重新定位指令代码: 需要连接
编译程序的结构
- 编译程序总框
-
遍:对源程序或源程序的中间表示从头到尾扫描一次
-
编译前端与后端
- 编译前端:与源语言有关,如词法分析,语法分析,语义分析与中间代码产生,与机器无关的优化
- 编译后端:与目标机有关,与目标机有关的优化,目标代码产生
编译程序的生成
以汇编语言和机器语言为工具
- 优点: 可以针对具体的机器,充分发挥计算机的系统功能;生成的程序效率高
- 缺点: 程序难读、难写、易出错、难维护、生产的效率低
高级语言书写
- 示例一:
- 已经有1,使用L1语言编写L2语言的编译器的代码2,将该代码使用1编译成L2语言编译器的可执行文件3
- 示例二:
- 已经有1,代码2使用1生成3,再使用代码4(也就是1)使用3生成5.
自编译方式
高级程序设计语言概述
下面哪种说法正确? ( )
A.标识符是语义概念,名字是语法概念
B.标识符是语法概念,名字是语义概念
答案B
-
语法:一组规则,用它可以形成和产生一个合式的程序
- 词法规则:单词符号的形成规则
- 语法规则:语法单位的形成规则
-
语义:一组规则,用它可以定义一个程序的意义
下面哪些属于程序语言的语义定义? ( )
A. 表达式中圆括号必须匹配
B. 类的声明必须以class开头
C. 关于函数调用时参数传递方法的描述
D. 函数体必须用return语句结尾 答案:C
下面说法的是错误的是( )
A.名字的绑定(binding)是指将标识符与所代表的程序数据或代码进行关联
B.名字的绑定总是发生在编译过程中
C.名字的绑定可以发生在运行过程中
答案:B
在C语言中,下面选项只具有右值、不具有左值的是 ( )。
A.变量
B.下标变量
C.a + 5
D.指针变量P
E.*P (P是指针变量)
答案:C
高级程序设计语言的语法描述
基本概念
-
字母表:一个有穷字符集,记为∑
-
字母表中每个元素称为字符
-
∑上的字(也叫字符串) 是指由∑中的字符所构成的一个有穷序列
-
不包含任何字符的序列称为空字,记为ε
-
用∑表示∑上的所有字的全体,包含空字ε。例如: 设 ∑={a, b},则 ∑*={ε,a,b,aa,ab,ba,bb,aaa,...}
-
∑*的子集U和V的连接(积)定义为
-
上下文无关文法
-
上下文无关文法G是一个四元组:G=(VT,VN,S,P),其中:
- VT:终结符(Terminal)集合(非空)
- VN:非终结符(Noterminal)集合(非空),且VT与VN不能重复
- S:文法的开始符号,S属于VN
- P:产生式集合(有限),每个产生式形式为
- 开始符S至少必须在某个产生式的左部出现一次
-
候选式的缩写
文法生成语言
- 直接推导
从文法到语言/语言到文法
推导与语法树
- 从一个句型到另一个句型的推导往往不唯一
- 最左推导:任何一步推导都是对式子中的最左非终结符进行替换
- 最右推导:任何一步推导都是对式子中的最右非终结符进行替换
- 语法树
语法树与二义性
- 一个句型不一定只对应唯一一棵语法树
- 文法的二义性:如果一个文法存在某个句子对应两棵不同的语法树,则说这个文法是二义的
形式语言鸟瞰
-
四种类型文法描述能力:0>1>2>3
词法分析器的设计--状态转换图
词法分析概述
-
词法分析的任务:从左至右逐个字符地对源程序进行扫描,产生一个个单词符号
-
词法分析器:执行词法分析的程序
-
输出的单词符号的表示形式:(单词种别,单词自身的值)
- 词法分析作为一个独立的阶段,但不一定不作为单独的一遍
词法分析器的结构
- 缓存长度
超前搜索
- 例如:
DO99K=1, 10与DO99K=1.10。第一个是DO 99 K = 1,10,第二个是DO99K是一个变量名。为了识别是DO还是DO99K,词法分析器需要超前搜索,第一个搜索到,才知道是循环结构,第二个搜索到.才能识别变量DO99K。这种超前搜索识别后还需要回退,费时间。 - 避免超前搜索的几点限制:
- 所有基本字都是保留字;用户不能用它们作自己的标识符
- 基本字作为特殊的标识符来处理,使用保留字表
- 如果基本字、标识符和常数(或标号)之间没有确定的运算符或界符作间隔,则必须使用一个空白符作间隔
状态转换图
-
状态转换图是一张有限方向图
- 结点代表状态,用圆圈表示
- 状态之间用箭弧连结,箭弧上的标记(字符)代表射出结状态下可能出现的输入字符或字符类
- 一张转换图只包含有限个状态,其中有一个为初态,至少要有一个终态
- 终态用两个同心圆标识。如果遇到终态识别后需要回退(重新读取刚刚的符号),需要加上星号
*
-
状态转换图可用于识别(或接受)一定的字符串
- 若存在一条从初态到某一终态的道路,且这条路上所有弧上的标记符连接成的字(字符串)等于,则称被该状态转换图所识别(接受)
- 词法分析器的设计示例
- 单词表
- 设计状态转换图
- 单词表
状态转换图的程序实现
- 不含回路的分叉结点
- 可用一个CASE语句或一组IF-THEN-ELSE语句实现
GetChar( );
if (IsLetter( ))
{…状态j的对应程序段…;}
else if (IsDigit( ))
{…状态k的对应程序段…;}
else if (ch=‘/’)
{…状态m的对应程序段…;}
else
{…错误处理…;}
- 含回路的状态结点
- 对应一段由WHILE结构和IF语句构成的程序
GetChar( );
while (IsLetter( )or IsDigit( ))
GetChar( );
…状态j的对应程序段…
- 终态结点
- 表示识别出某种单词符号,对应返回语句
RETURN (C,VAL)
/* 其中,C为单词种别,VAL为单词自身值 /*
-
全局变量与过程
ch字符变量,存放最新读入的源程序字符strToken字符数组,存放构成单词符号的字符串GetChar子程序过程,把下一个字符读入到 ch 中GetBC子程序过程,跳过空白符,直至 ch 中读入一非空白符Concat子程序,把ch中的字符连接到 strTokenIsLetter和IsDisgital布尔函数,判断ch中字符是否为字母和数字Reserve整型函数,对于 strToken 中的字符串查找保留字表,若它是保留字则给出它的编码,否则回送0Retract子程序,把搜索指针回调一个字符位置InsertId整型函数,将strToken中的标识符插入符号表,返回符号表指针InsertConst整型函数过程,将strToken中的常数插入常数表,返回常数表指针
-
例子:
int code, value;
strToken := “ ”; /*置strToken为空串*/
GetChar(); GetBC();
if (IsLetter())
begin
while (IsLetter() or IsDigit())
begin
Concat(); GetChar();
end
Retract();/*注意回退*/
code := Reserve();
if (code = 0)
begin
value := InsertId(strToken);
return ($ID, value);
end
else
return (code, -);
end
else if (IsDigit())
begin
while (IsDigit())
begin
Concat( ); GetChar( );
end
Retract();
value := InsertConst(strToken);
return($INT, value);
end
else if (ch =‘=’) return ($ASSIGN, -);
else if (ch =‘+’) return ($PLUS, -);
else if (ch =‘*’)
begin
GetChar();
if (ch =‘*’) return ($POWER, -);
Retract(); return ($STAR, -);
end
else if (ch =‘,’) return ($COMMA, -);
else if (ch =‘(’) return ($LPAR, -);
else if (ch =‘)’) return ($RPAR, -);
else ProcError( ); /* 错误处理*/
- 将状态图的代码一般化
- 变量curState用于保存现有的状态
- 用二维数组表示状态图:stateTrans[state][ch]
curState = 初态
GetChar();
while( stateTrans[curState][ch]有定义){
//存在后继状态,读入、拼接
Concat();
//转换入下一状态,读入下一字符 curState= stateTrans[curState][ch];
if curState是终态 then 返回strToken中的单词
GetChar( );
}
词法规则的形式化
正规集和正规式
- 正规集和正规式
- 正规集可以用正规式表示
- 正规式是表示正规集一种方法
- 一个字集合是正规集当且仅当它能用正规式表示
正规式和正规集的递归定义
- 对给定的字母表∑,仅由有限次使用上述三步骤而定义的表达式才是∑上的正规式,仅由这些正规式表示的字集才是∑上的正规集。
- ε和∅都是∑上的正规式,它们所表示的正规集为{ε}和∅;
- 任何a∈∑,a是∑上的正规式,它所表示的正规集为{a} ;
- 假定e1和e2都是∑上的正规式,它们所表示的正规集为L(e1)和L(e2),则
- (e1|e2)为正规式,它所表示的正规集为L(e1)∪L(e2)
- (e1.e2)为正规式,它所表示的正规集为L(e1)L(e2)
- (e1)*为正规式,它所表示的正规集为(L(e1))*
ε是什么?
A. 字符
B. 字
C. 正规式
答案:BC
∅是什么?
A. 集合
B. 字
C. 正规式
答案:AC
a(a∈∑)是什么?
A.字符
B. 字
C. 正规式
答案:ABC
正规式的等价
- 若两个正规式所表示的正规集相同,则称这两个正规式等价。
- 正规式的性质
确定有限自动机(DFA)
- 定义:
-
DFA表示为状态转换图
- 假定DFA M含有m个状态和n个输入字符
- 对应的状态转换图含有m个状态结点,每个结点顶多含有n条箭弧射出,且每条箭弧用Σ上的不同的输入字符来作标记
-
对于∑*中的任何字α,若存在一条从初态到某一终态的道路,且这条路上所有弧上的标记符连接成的字等于α,则称α为DFA M所识别(接收)。DFA M所识别的字的全体记为L(M)
图中DFA M识别的L(M) 是什么?
A. L(M)={以aa或bb开头的字}
B. L(M)={含aa或bb的字}
C. L(M)={以aa或bb结尾的字}
答案:B
哪个DFA识别{ε} ?
答案:A
- DFA的程序实现
非确定有限自动机(NFA)
- 定义:
- 从状态图看NFA 和DFA的区别
- NFA可以有多个初态
- 弧上的标记可以是∑*中的一个字(甚至可以是一个正规式),而不一定是单个字符
- 同一个字可能出现在同状态射出的多条弧上
- 对于∑*中的任何字α,若存在一条从初态到某一终态的道路,且这条路上所有弧上的标记字连接成的字等于α(忽略那些标记为ε的弧),则称α为NFA M所识别(接收)
- NFA M所识别的字的全体记为L(M)
图中DFA M识别的L(M) 是什么?
A. L(M)={以aa或bb开头的字}
B. L(M)={含aa或bb的字}
C. L(M)={以aa或bb结尾的字}
答案:B
图中NFA M2识别的L(M2) 是什么?
A. L(M2)={ | n≥1}
B. L(M2)={ | n≥1}
C. L(M2)={ | m,n≥1}
答案:C
- DFA与NFA
- 对于任何两个有限自动机M和M’,如果L(M)=L(M’),则称M与M’等价
- 对于每个NFA M存在一个DFA M’,使得 L(M)=L(M’)
有限自动机的等价性——NFA转换成DFA
等价性证明
- 对M的状态转换图进一步施行替换,其中k是新引入的状态。
NFA确定化
- 子集法
例子:
- 确定化:不失一般性,设字母表只包含两个字符a和b,我们构造一张计算状态集的转换表:
- 首先,置第1行第1列为ε-closure({X})求出这一列的Ia,Ib;
- 然后,检查这两个Ia,Ib,看它们是否已在表中的第一列中出现,把未曾出现的填入后面的空行的第1列上,求出每行第2,3列上的集合...
- 重复上述过程,直到所有第2,3列子集全部出现在第一列为止
- 转换表唯一刻划了一个确定的有限自动机M
- 初态是ε-closure({X})
- 终态是含有原终态Y的子集
- 不难看出,这个DFA M与M’等价
DFA的化简
-
DFA的化简(最小化)
- 对于给定的DFA M,寻找一个状态数比M少的DFA M’,使得L(M)=L(M’)
-
状态的等价性:假设s和t为M的两个状态,称s和t等价:如果从状态s出发能读出某个字α而停止于终态,那么同样,从t出发也能读出α而停止于终态;反之亦然
- 两个状态不等价,则称它们是可区别的
两个状态s和t是可区分的,是指( )
A. 对于任意字α,要么s读出α停止于终态而t读出α停止于非终态,要么t读出α停止于终态而s读出α停止于非终态
B. 存在一个字α,要么s读出α停止于终态而t读出α停止于非终态,要么t读出α停止于终态而s读出α停止于非终态
答案:B
- 基本思想
- 把M的状态集划分为一些不相交的子集,使得任何两个不同子集的状态是可区别的,而同一子集的任何两个状态是等价的。
- 最后,让每个子集选出一个代表,同时消去其他状态。
按照上述原则对DFA的状态集合S进行第一次划分,正确的分法是( )
A. 初态和非初态
B. 终态和非终态
C. 初态、终态、其他状态
答案:B
- 例子:找出终态与非终态,然后不断划分,直到划不动为止。从每一个子集中选一个状态代表这个子集,原来子集中所有射入射出的线归为这个状态所有。
正规式与有限自动机的等价性
为NFA构造正规式
- 然后,反复使用下面的三条规则,逐步消去结点,直到只剩下X和Y为止。
复杂的消去
- 最后,X到Y的弧上标记的正规式即为所构造的正规式r。显然L(r)=L(M’)=L(M)
为正规式构造NFA
- 定理: 对于∑上的正规式r,都存在一个NFA M,使L(M)=L(r),并且M只有一个初态和一个终态,而且没有从终态出发的箭弧
- 证明:归纳法
- 首先,把正规式r表示成:
- 按下面的三条规则对r进行分裂
- 逐步把这个图转变为每条弧只标记为∑上的一个字符或ε,最后得到一个NFA M’,显然L(M’)=L(r)
- 转换完后,还可以把这个NFA进一步转化为DFA并化简。
- 转换完后,还可以把这个NFA进一步转化为DFA并化简。
语法分析基本概念
-
对语言的语法结构进行描述
- 采用正规式和有限自动机描述和识别语言的单词符号
- 用上下文无关文法来描述语法规则
-
语法分析的任务
- 分析一个文法的句子的结构
-
语法分析器的功能
- 按照文法的产生式(语言的语法规则),识别输入符号串是否为一个句子(合式程序)
-
语法分析器的结构
- 语法分析的方法
| 自下而上(Bottom-up) | 自上而下(Top-down) |
|---|---|
| 从输入串开始,逐步进行归约,直到文法的开始符号 | 从文法的开始符号出发,反复使用各种产生式,寻找"匹配"的推导 |
| 归约:根据文法的产生式规则,把串中出现的产生式的右部替换成左部符号 | 推导:根据文法的产生式规则,把串中出现的产生式的左部符号替换成右部 |
| 从树叶节点开始,构造语法树 | 从树的根开始,构造语法树 |
| 算符优先分析法、LR分析法 | 递归下降分析法、预测分析程序 |
自上而下分析
- 基本思想
- 从文法的开始符号出发,向下推导,推出句子
- 针对输入串,试图用一切可能的办法,从文法开始符号(根结点)出发,自上而下地为输入串建立一棵语法树
文法左递归问题
- 一个文法是含有左递归的,如果存在非终结符P,如果有产生式
P→Pa|b,那么程序可能一直推导P→Pa,一直递归下去……
消除文法的左递归
-
一个文法消除左递归的条件
- 不含以ε为右部的产生式
- 不含回路
-
间接左递归的消除
- 消除左递归的算法
回溯问题
- 分析过程中,当一个非终结符用某一个候选匹配成功时,这种匹配可能是暂时的。出错时,不得不“回溯”
例如,上图中,有推导:
A→**|*。但是输入串是x*y。当符号指针指向表达式中的*时,根据推导式,会首先匹配A→**,但显然当IP(符号指针)指向y时,就与推导式中的**不匹配。因此就得回溯,转而匹配A→*。
消除回溯
- 对文法的任何非终结符,当要它去匹配输入串时,能够根据它所面临的输入符号准确地指派它的一个候选去执行任务,并且此候选的工作结果应是确信无疑的,不用选了个错的候选又得回来。
FIRST集合
- 定义:
- 为了满足所有候选首符集两两不相交,需要提取公共左因子:
- 构造FIRST集合:
构造任何符号串的FIRST集合
FOLLOW集合
-
有的产生式会产生ε。有时候,我们需要匹配ε来选择其他的产生式的候选(如下图)。那么,什么时候需要匹配呢?引入FOLLOW集合。
-
定义:
-
构造每个非终结符的FOLLOW集合
LL(1)文法
-
定义:
-
分析符号串
递归下降分析程序
-
分析程序由一组子程序组成, 对每一语法单位(非终结符)构造一个相应的子程序,识别对应的语法单位
-
定义全局过程和变量
ADVANCE,把输入串指示器IP指向下一个输入符号,即读入一个单词符号SYM,IP当前所指的输入符号ERROR,出错处理子程序
-
A→TE'|BC|ε,对应的递归下降子程序为:
PROCEDURE A;
BEGIN
IF SYM∈FIRST(TE’) THEN
BEGIN T;E END
ELSE IF SYM∈FIRST(BC) THEN
BEGIN B; C END
ELSE IF SYM∈FOLLOW(A) THEN
BEGIN END
ELSE ERROR
END;
案例
PROCEDURE F;
IF SYM=‘i’ THEN ADVANCE
ELSE
IF SYM=‘(’ THEN
BEGIN
ADVANCE;
E;
IF SYM=‘)’ THEN ADVANCE
ELSE ERROR
END
ELSE ERROR;
PROCEDURE E;
BEGIN
T;E’
END;
PROCEDURE E';
IF SYM=‘+’ THEN
BEGIN
ADVANCE;
T;E'
END
ELSE IF SYM<>‘#’ AND SYM<>’)’
THEN ERROR
PROCEDURE T;
BEGIN
F;T'
END
PROCEDURE T';
IF SYM=‘*’ THEN
BEGIN
ADVANCE;
F;T'
END
ELSE IF SYM<>‘#’ AND SYM<>’)’ AND SYM<>’+’
THEN ERROR
主程序:
PROGRAM PARSER;
BEGIN
ADVANCE;
E;
IF SYM <>’#’ THEN
ERROR
END;
思考:如果把E'()改写成如下形式:
PROCEDURE E'; IF SYM=‘+’ THEN BEGIN ADVANCE; T;E' ENDE'实现了ε吗 ?()
A. 实现了
B. 没实现
答案:A
遇到ε也就是什么也不匹配,进而转到其他程序继续匹配。
上面的代码中,没有考虑Follow集合,也没有ERROR出错处理语句。E'不考虑Follow集合有问题吗 ?()
A. 有问题
B. 没有问题
答案:B 在这里E'结束后会跳到其他函数。如果输入串有错误,在其他函数里照样会检测出来。
扩充的巴科斯范式和语法图
- 扩充的巴科斯范式:
例如,通常的“实数”可定义为:
Decimal→[Sign]Integer.{digit}[Exponent] Exponent→E[Sign]Integer Integer→digit{digit} Sign→ + | -
预测分析程序
原理
- 总控程序,根据现行栈顶符号和当前输入符号,执行动作
- 分析表 M[A,a]矩阵,A ∈ ,a ∈ 是终结符或‘#’
- 分析栈 STACK 用于存放文法符号
- 若X=a=‘#’,则宣布分析成功,停止分析。
- 若X=a ≠‘#’,则把X从STACK栈顶逐出,让a指向下一个输入符号。
- 若X是一个非终结符,则查看分析表M。
- 若M[X,a]中存放着关于X的一个产生式,把X逐出STACK栈顶,把产生式的右部符号串按反序依次推进STACK栈(产生式右部箭头的第一个符号放栈顶)(若右部符号为ε ,则意味不推什么东西进栈)。
- 若M[X,a]中存放着“出错标志”,则调用出错诊察程序ERROR。
- 程序实现
BEGIN
首先把‘#’然后把文法开始符号推进STACK栈;
把第一个输入符号读进a;
FLAG:=TRUE;
WHILE FLAG DO
BEGIN
把STACK栈顶符号上托出去并放在X中;
IF X∈VT THEN
IF X= a
THEN 把下一输入符号读进a
ELSE ERROR
ELSE IF X=‘#’ THEN
IF X=a
THEN FLAG:=FALSE
ELSE ERROR
ELSE IF M[X,a]={X→X1X2…Xk}THEN
把Xk,Xk-1,…,X1一一推进STACK栈
/* 若X1X2…Xk=ε,不推什么进栈 */
ELSE ERROR
END OF WHILE;
STOP /*分析成功,过程完毕*/
END
构造预测分析表
- 构造G的分析表M[A,a], 确定每个产生式A→α在表中的位置
- 对文法G的每个产生式A→α执行第2步和第3步;
- 对每个终结符a∈FIRST(α),把A→α加至M[A,a]中;
- 若ε∈FIRST(α),则对任何b∈FOLLOW(A)把A→α加至M[A,b]中。
- 把所有无定义的M[A,a]标上“出错标志”。
综合案例:
LL(1)文法与二义性
- 如果G是左递归或二义的,那么,M至少含有一个多重定义入口。因此,消除左递归和提取左因子将有助于获得无多重定义的分析表M。
- 可以证明,一个文法G的预测分析表M不含多重定义入口,当且仅当该文法为LL(1)的。
- LL(1)文法不是二义的。
实际上,程序语言中,有许多文法是二义的。例如:
G(S): S → iCtS | iCtSeS | a C → b这是一个
if … then ... else …的文法。i代表if,t代表then,e代表else。提取左因子之后,改写成:
G(S): S → iCtSS’ | a S’→ eS | ε C → b构造预测分析表如下: 可以看到,在[S',e]中,有两个产生式。如果交给程序,显然这是不合理的。实际上,在程序中会遇到如下的是的语句:
if ... then if ... then ... else …这里可以解释为else与就近的then匹配。
if ... then if ... then ... else ...或者与远端的then匹配:
if ... then if ... then ... else ...大多数程序会选择else与就近的then匹配。在实际中,编译程序会将预测分析表中冲突的地方手动指定要选择的产生式。
自下而上分析
- 采用“移进-归约”思想进行自下而上分析
- 用一个寄存符号的先进后出栈,把输入符号一个一个地移进到栈里,当栈顶形成某个产生式的候选式时,即把栈顶的这一部分替换成(归约为)该产生式的左部符号。
你认为什么是可归约串?
A. 连续出现的单词序列
B. 短语
答案:B
-
短语:
-
直接短语:
-
小技巧:在一个句型对应的语法树中
- 以某非终结符为根的两代以上的子树的所有末端结点从左到右排列就是相对于该非终结符的一个短语
- 如果子树只有两代,则该短语就是直接短语
-
分析过程描述
算符优先分析方法
-
-
一个文法,如果它的任一产生式的右部都不含两个相继(并列)的非终结符,即不含
…QR…形式的产生式右部,则我们称该文法为算符文法。- 约定:
- a、b代表任意终结符
- P、Q、R代表任意非终结符
- ‘…’代表由终结符和非终结符组成的任意序列,包括空字
- 约定:
-
FIRSTVT和LASTVT集合
- 根据FIRSTVT和LASTVT集合,检查每个产生式的候选式,确定满足关系
<和>的所有终结符对- 假定有个产生式的一个候选形为
…aP…, 那么,对任何b∈FIRSTVT(P),有a<b - 假定有个产生式的一个候选形为
…Pb…, 那么,对任何a∈LASTVT(P),有a>b
- 假定有个产生式的一个候选形为
构造集合FIRSTVT(P)、LASTVT(P)的算法
-
反复使用下面两条规则构造集合FIRSTVT(P)
- 若有产生式
P→a…或P→Qa…,则a∈FIRSTVT(P) - 若
a∈FIRSTVT(Q),且有产生式P→Q…,则a∈FIRSTVT(P)
- 若有产生式
-
反复使用下面两条规则构造集合LASTVT(P)
- 若有产生式
P→…a或P→…aQ,则a∈LASTVT(P) - 若
a∈LASTVT(Q),且有产生式P→…Q,则a∈LASTVT(P)
- 若有产生式
- 算法的一种实现
- 布尔数组F[P,a],使得F[P,a]为真的条件是,当且仅当a∈FIRSTVT(P)。开始时,按上述的规则1对每个数组元素F[P,a]赋初值。
- 栈STACK,把所有初值为真的数组元素F[P,a]的符号对(P,a)全都放在STACK之中。
- 若栈STACK不空,就将栈顶项弹出,记此项为
(Q,a)。对于每个形如P→Q…的产生式,若F[P,a]为假,则变其值为真且将(P,a)推进STACK栈。 - 上述过程一直重复,直至栈STACK为空为止。
PROCEDURE INSERT(P,a)
IF NOT F[P,a] THEN
BEGIN
F[P,a]:=TRUE;
把(P,a)下推进STACK栈
END;
- 主程序:
BEGIN
FOR 每个非终结符P和终结符a DO
F[P,a]:=FALSE;
FOR 每个形如P→a…或P→Qa…的产生式 DO
INSERT(P,a);
WHILE STACK 非空 DO
BEGIN
把STACK的顶项,记为(Q,a),上托出去;
FOR 每条形如P→Q…的产生式 DO
INSERT(P,a);
END OF WHILE;
END
构造优先关系表
- 构造优先关系表的算法
FOR 每条产生式P→X1X2…Xn DO
/*P → X1 X2 … Xi Xi+1 Xi+2 … Xn*/
FOR i:=1 TO n-1 DO
BEGIN
IF Xi和Xi+1均为终结符 THEN 置Xi=Xi+1
IF i<=n-2且Xi和Xi+2都为终结符, 但Xi+1为非终结符 THEN
置Xi=Xi+2;
IF Xi为终结符而Xi+1为非终结符 THEN
FOR FIRSTVT(Xi+1)中的每个a DO
置 Xi<a;
IF Xi为非终结符而Xi+1为终结符 THEN
FOR LASTVT(Xi)中的每个a DO
置 a>Xi+1
END
最左素短语
- 一个文法G的句型的素短语是指这样一个短语,它至少含有一个终结符,并且,除它自身之外不再含任何更小的素短语
- 最左素短语是指处于句型最左边的那个素短语
最左素短语定理
理解:
算符优先分析算法的实现
- 使用一个符号栈S,用它寄存终结符和非终结符,k代表符号栈S的使用深度
- 在正确的情况下,算法工作完毕时,符号栈S应呈现:# N #
对于文法的句子来说,它的算符优先分析的结果就是语法树
A. 正确
B. 错误
答案:B
算符优先分析程序构成
LR分析法
-
LR分析法
- L:从左到右扫描输入串
- R:自下而上进行归约
-
工作框架
句柄和规范归约
- 句柄:
- 在一个句型对应的语法树中,最左两代子树末端就是句柄
-
规范归约
- 规范归约是最左归约
- 规范归约的逆过程就是最右推导:
S ⇒ aAcBe ⇒ aAcde ⇒ aAbcde ⇒ abbcde - 最右推导也称为规范推导
- 由规范推导推出的句型称为规范句型
LR分析器
- LR分析器的结构
- LR分析方法:把"历史"及"展望"综合抽象成状态;由栈顶的状态和现行的输入符号唯一确定每一步工作
LR分析表
- ACTION[s,a]:当状态s面临输入符号a时,应采取什么动作.
- GOTO[s,X]:状态s面对文法符号X时,下一状态是什么
LR分析过程
- 分析开始时: (,#, #)
所生成的归约得到的树如下:
LR文法
- 定义:对于一个文法,如果能够构造一张分析表,使得它的每个入口均是唯一确定的,则这个文法就称为LR文法。
- 定义:一个文法,如果能用一个每步顶多向前检查k个输入符号的LR分析器进行分析,则这个文法就称为LR(k)文法.
LR分析器的性质
- 栈内的符号串和扫描剩下的输入符号串构成了一个规范句型一旦栈的顶部出现可归约串(句柄),则进行归约
对于句子,在规范归约过程中,栈内的符号串和扫描剩下的输入符号串构成了一个规范句型,下面哪种格局不会出现:
答案:D。如果句柄出现在栈顶,分析器会立即进行规约,不会让它进入栈内。
构造LR分析表
活前缀
- 字的前缀:是指字的任意首部,如字abc的前缀有 ε,a,ab,abc
- 活前缀:是指规范句型的一个前缀,这种前缀不含句柄之后的任何符号。即,对于规范句型αβγ,β为句柄,如果αβ=u1u2…ur,则符号串u1u2…ui(1<=i<=r)是αβγ的活前缀。(γ必为终结符串)
- 规范归约过程中,保证分析栈中总是活前缀,就说明分析采取的移进/归约动作是正确的。(也就是栈顶总会有句柄可以归约)
构造识别活前缀的DFA
-
将文法G(S)拓广为G‘(S‘)
- 构造文法G‘,它包含了整个G,并引进不出现在G中的非终结符S‘、以及产生式S‘→S,S‘是G‘的开始符号
- 称G‘是G的拓广文法
-
LR(0)项目:在每个产生式的右部添加一个圆点,表示我们在分析过程中看到了产生式多大部分
-
A→XYZ有四个项目:A→•XYZ(代表此时即将读入XYZ)、A→X•YZ(代表已经读入X,即将读入YZ。X已经进栈)、A→XY•Z、A→XYZ•(代表XYZ已经读入。都已经进栈) -
构造识别文法所有活前缀的DFA
- 构成识别一个文法活前缀的DFA的项目集(状态)的全体称为文法的LR(0)项目集规范族。
构造LR(0)分析表
-
LR(0)文法:假若一个文法G的拓广文法G'的活前缀识别自动机中的每个状态(项目集)不存在下述情况:
- 既含移进项目又含归约项目;
- 含有多个归约项目
-
LR(0)分析表的构造
基本完成 2023-02-28