《编译原理》-用例题理解-自顶向下语法分析及 FIRST,FOLLOW,SELECT集,LL(1)文法

《编译原理》-用例题理解-自顶向下语法分析及 FIRST,FOLLOW,SELECT集,LL(1)文法

此编译原理肯定某高级程序设计语言编译原理,理论基础,学习笔记java

本笔记是对教材《编译原理》- 张晶老师版 作学习笔记。数组

最近在学《编译原理》,前三章感受还能够理解,到了第四章就感受这难度就上来了。就是说过了词法分析,刚到语法分析,就开始头大了,因而想作个笔记,本篇就是第 4 章的笔记。函数

(一)前言

第4章 - 自顶向下的语法分析学习

语法分析设计

语法分析是在词法分析识别出的单词符号串的基础上,分析并断定句子的语法结构是否符合语法规则3d

自顶向下分析法code

自顶向下分析法就是从文法的开始符号出发,不断创建直接推导,试图构造一个最左推导序列,最终由它推导出与输入符号串彻底匹配(相同)的句子。blog

从语法树的角度看,自顶向下分析法就是以开始符号为根节点,试图向下构造一棵语法树,其端末结符号串与输入符号串相同递归

问题1:左递归问题

若采用自顶向下的语法分析,应消除文法中存在的左递归。token

由于左递归的存在,有可能使推导不能结束,分析陷入循环状态。
例如:A → Aa | b

左递归还比较好理解,例如:我须要匹配的字符串是 bsd 就须要从左端 b 开始是被,推导时,先用 A -> Aa 推导 AAa,此时不符合条件,但由于还有非终结符,不能结束,而是继续推导。此时就会陷入死循环。

因此要避免这类状况就要消除左递归。

消除文法的左递归

课本上直接左递归,间接左递归,提取左公因子书上比较详细,通常能够理解,跳过了

问题2:回溯问题

由于推导时,右侧有的状况,从各类可能的选择中随机挑选一种,并但愿它是正确的。若是之后发现它是错误的,必须退回去,再试另外的选择这种方式称为回溯

回溯代价极高,效率很低,因此也要避免。

因此须要FIRST 集,FOLLOW 集和 SELECT 集,这一堆集出马了。

(二)关于 FIRST 集 - 首终结符集

若是一个文法的每一个产生式的右部都由终结符开始,有相同左部的产生式,它们的右部由不一样的终结符开始,这样的文法在推导过程当中就能够根据当前的输入符号来决定选择哪一个产生式往下推导,它的分析过程是惟一肯定的,不会产生回溯现象

简单的说,右部都以非终结符开头,每一个产生式的右部第一个非终结符又都不同就更好了,咱们就能够根据语法惟一的选择产生式,

例如:须要识别输入符号串 bsd

文法:
(1)S -> aBC
(2)B -> cC
(3)C -> bB
(4)C -> d
......
咱们知道 b 就能够选择 (3)

可是通常文法并不知足上述格式,例如:
S -> Af | Be
此时,还想使用相同方法,咱们能够经过肯定 Af 和 Be 推到最后的首终结符集(FIRST 集)来肯定。

FIRST 集的定义

简单的说 FIRST 集就是一个文法符号串的开始符号集合

设 G=(VT,VN,S,P)是上下文无关文法,
FIRST(α)={a|α=>aβ,a∈VT,α,β∈v*}
若 α=
> ε(通过0或多步推导能够推出为空串),则规定 ε ∈ FIRST(α)

FIRST(α) 是 α 的全部可能推导的开头终结符或可能的 ε。

例题 4.3:求 FIRST 集

题目:

给定文法 G[S]:
(1)S -> Af
(2)S -> Be
(3)A -> a
(4)A -> cA
(5)B -> b
(6)B -> dB

详解:

求(1)中的 Af 的 FIRST 集,注意,由于若是推出为空时用 ε,因此 A 后面的 f 是没用的,咱们只分析 A 的第一个终结符的集。
由于(3)和(4)都是由 A 推导,因此两个都考虑
FIRST(Af) = FIRST(a) ∪ FIRST(cA) = {a,c}

同理可求出:
FIRST(Be)
FIRST(a)
FIRST(cA)
FIRST(dB)

(三)关于 FOLLOW 集 - 后随集

若是仅适用 FIRST 只能根据首字符不一样选择产生式,若是首字符不一样...

FOLLOW 集的定义

简单的说 FOLLOW 集就是一个文法符号的后跟终结符号的集合。

设 G =(VT,VN,S,P)是上下文无关文法,A∈VN,S是开始符号。
FOLLOW(A)={a|S=>*…Aa…,a ∈VT}
如有 S=>*…A(就是说 A 已是最后一个时,没有后面的),则规定 # ∈ FOLLOW(A)

FOLLOW(A) 是全部出如今紧接 A 以后的终结符或 “#”;

FOLLOW 计算规则

(1) 对于文法的开始符号 S,置 # 到 FOLLOW(S) 中;
(2)若 A -> αBaβ 是一个产生式,a 为终结符,则把 a 加至 FOLLOW(B) 中;
(3)若 A -> αBβ 是一个产生式,则把 FIRST(β) - {ε} 加至 FOLLOW(B) 中;
(4)若 A -> αB 是一个产生式,或 A -> αBβ 是一个产生式,而 β =*> ε,
则把 FOLLOW(A) 加至 FOLLOW(B) 中

提示:
(1)就是说若是对开始符号求 FOLLOW(S) ,直接来个 # ∈FOLLOW(S) ,不过要表示成 {#}
(2)就是把后面的紧跟的终结符,就直接加到 FOLLOW 集
(3)正经的求 B 的 FOLLOW 集,就是 B 后面 β 的 FIRST(β) - {ε}
(4)分状况:

  • 若是 A -> αB,就把 FOLLOW(A) 加至 FOLLOW(B) 中
  • A -> αBβ 是一个产生式,此时 β 能够推成 ε,就是至关于也能推出 A -> αB,也把 FOLLOW(A) 加至 FOLLOW(B) 中

注意: (4)中这里是 FOLLOW(A) 加至 FOLLOW(B) ,就是左部的 FOLLOW 集,加到其推导出的右部的最后一个非终结符的 FOLLOW 集,
例如:须要识别输入符号串 :bsAcD,求 FOLLOW(B) 的时候
此时 FOLLOW(A) 就含有 c,若是 A -> dB,即此时 FOLLOW(B) 也应该有 c

记忆方式:
提示:这是规则,不是求某个固定谁的 FOLLOW 集,而涉及多个非终结符的 FOLLOW 集,因此建议对每一个产生式对这 4 个规则都要考虑,否则很容易漏。
规则(1)看左侧为开始符;
规则(2)右侧看 B 后是否紧跟终结符;
规则(3)右侧看 B 后紧跟的是否有非终结符
规则(4)右侧看 B 是否是最后一个,或 B 后面的能够推出空串,间接最后一个

例题 4.4:求 FOLLOW 集

题目:

给定文法 G[S]:
(1)S -> eT|RT
(2)T -> DR|ε
(3)R -> dR|ε
(4)D -> a|bd

详解:
计算时,要同时考虑四个规则是否知足,就是都要考虑
对产生式(1):

  • 1.知足规则(1),由于 S 是开始符号,能够获得 FOLLOW(S) = {#}
  • 2.不知足规则(2)
  • 3.知足规则(3),对 S -> RT,应把 FIRST(T) - {ε} = {a,b} 加到 FOLLOW(R);
  • 4.知足规则(4),将 FOLLOW(S)={#} 加到 FOLLOW(T)

对产生式(2):

  • 1.不知足规则(1)
  • 2.不知足规则(2)
  • 3.知足规则(3),对 T -> DR,应把 FIRST(R) - {ε} = {d} 加到 FOLLOW(D);
  • 4.知足规则(4),将 FOLLOW(T)={#} 加到 FOLLOW(R)

对产生式(3):

  • 1.不知足规则(1)
  • 2.不知足规则(2)
  • 3.不知足规则(3)
  • 4.不知足规则(4),先后 R 和 R 同样不用加

对产生式(4):

  • 1.不知足规则(1)
  • 2.不知足规则(2)
  • 3.不知足规则(3)
  • 4.不知足规则(4)

最终结果:

FOLLOW(S) = {#}
FOLLOW(T) = {#}
FOLLOW(R) = {a, b, #}
FOLLOW(D) = {d, #}

(三)关于 SELECT 集 - 可选集

首先

SELECT 集的定义

注意区分 α 和 A

给定文法 G,对于产生式 A→α,α ∈ V*,则可选集 SELECT(A→α) 有:
(1)若 α ≠ ε,且 α ≠+ ε,则 SELECT(A→α) = FIRST(α)
(2)若 α ≠ ε,但 α =+ ε,则 SELECT(A→α) = FIRST(α) ∪ FOLLOW(A)
(3)若 α = ε,则 SELECT(A→α) = FOLLOW(A)

描述(关键):
(1)第1条说,α ≠ ε,且经过1次或屡次推不出 ε,SELECT(A→α) = FIRST(α)
(2)第2条是,α ≠ ε,α 经有限步可推出 ε,注意是一个 α,一个 A,SELECT(A→α) = FIRST(α) ∪ FOLLOW(A)
(3)第3条是说若是 α 自己就是 ε,SELECT(A→α) = FOLLOW(A)。

用表达式描述:

例题 4.5:求 SELECT 集

题目:

给定文法 G[S]:
(1)S -> aA
(2)S -> c
(3)A -> aAS
(4)A -> ε

详解:
对(1)使用规则(1),得 SELECT(S -> aA) = FISRT(aA) = {a},此时虽然 A 能够推出 ε,但规则中指的是总体 aA
对(2)使用规则(1),得 SELECT(S -> c) = FISRT(c) = {c}
对(3)使用规则(1),得 SELECT(A -> bAS) = FISRT(bAS) = {b}
对(4)使用规则(3),得 SELECT(A -> ε) = FOLLOW(A) = {a, c, #}
下面再回顾一下 FOLLOW 集的求法。

对本题求 FOLLOW(A) ,先求出 FIRST(S) = {a, c}:
对产生式(1):

  • 1.知足规则(1),S 是开始符号,FOLLOW(S) 为 {#}
  • 2.不知足规则(2)
  • 3.不知足规则(3)
  • 4.知足规则(4),FOLLOW(S) = {#} 加到 FOLLOW(A)

对产生式(2):

  • 1.不知足规则(1)
  • 2.不知足规则(2)
  • 3.不知足规则(3)
  • 4.不知足规则(4)

对产生式(3):

  • 1.知足规则(1)
  • 2.不知足规则(2)
  • 3.不知足规则(3),FIRST(S) - {ε} = {a, c} 加到 FOLLOW(A)
  • 4.不知足规则(4)

对产生式(4):

  • 1.不知足规则(1)
  • 2.不知足规则(2)
  • 3.不知足规则(3)
  • 4.不知足规则(4)

综合能够看出 FOLLOW(A) = {a, c, #}

(四)关于 LL(1) 文法

LL(1) 文法名称的含义:
名称| 含义
--- | ----
第一个L | 从左到右扫描输入串
第二个L | 使用最左推导方法
1 | 只需查看一个输入符号即可决定选择哪一个产生式进行推导

文法是 LL(1) 文法的充分必要条件:
若某文法是LL(1)文法,那么它可以惟一肯定选用的产生式

判断文法是不是 LL(1) 文法步骤以下:

  1. 求出能推出ε的非终结符;
  2. 计算FIRST集;
  3. 计算FOLLOW集;
  4. 计算SELECT集;
  5. SELECT集相交是否为空。
例题 4.6:判断文法是不是 LL(1) 文法

题目:

给定文法 G[S]:
(1)E -> TE'
(2)E' -> ATE'|ε
(3)T -> FT'
(4)T' -> MFT'|ε
(5)F -> (E)|i
(6)A -> +|-
(7)M -> *|/

答案:
FIRST 集,FOLLOW 集,SELECT 集以下
在这里插入图片描述
(图片来自教材:《编译原理》张晶老师版)

很是要注意的是:

咱们知道判断是否为 LL(1) 文法条件是:根据同一非终结符的 SELECT 集是否相交,相交不是,不相交则是。
那么 E 和 E' 的统一非终结符吗?LL(1) 是否为空,要比较 SELECT(E) 和 SELECT(E') 吗?
答案是:不是统一非终结符,不用也不能比较他俩。由于他俩没有关系,E' 相似因而中间变量,用其余的字母替换也不影响此文法的功能

详解:
由于:
SELECT(E' -> ATE') ∩ SELECT(E' -> ε)= ∅
SELECT(T' -> MFT') ∩ SELECT(T' -> ε)= ∅
SELECT(F -> (E)) ∩ SELECT(F -> i)= ∅
SELECT(A -> +) ∩ SELECT(A -> -)= ∅
SELECT(M -> *) ∩ SELECT(M -> /)= ∅

因此 SELECT 集相交是否为空,文法 G[S] 是 LL(1) 文法

(五)关于 LL(1) 分析法(预测分析法)

LL(1) 分析法,也称预测分析法,采用这种方法的分析器由一张分析表、一个分析栈和一个控制程序组成,图形化比表示为:

(图片来自教材:《编译原理》张晶老师版)

这个语法分析过程彻底由预先根据文法设计的分析表 M 以及 分析栈S 进行控制(控制程序)

分析表和控制程序: 对于不一样的文法,会有不一样的分析表 M,但这种语法分析方法的总控程序是同样的。
分析栈: 分析栈 S 用于存放文法符号。分析开始时,栈底先放一个 ‘ # ’,而后,放进文法的开始符号。随着分析的展开,放入相应符号。

关于分析表:
(1)分析表是一个二维数组 M[A,a],其中 A 是非终结符,a 是终结符或 #。
(2)M[A,a] 中如有产生式,代表 A 可用该产生式推导,以求与输入符号 a 匹配。
(3)M[A,a] 中若为空,代表 A 不可能推导出与 a 匹配的字符串。

怎么求分析表:

对文法 G 的每一个产生式 A->α 执行如下步骤:
(1)若 a∈SELECT (A->α), 则把 A->α 加至 M[A,a] 中;
(2)把全部无定义的 M[A,a] 标上“出错标志”。
为了使表简化,表中空白处为出错。

关于控制程序:
控制程序在任什么时候候都是按分析栈栈顶符号 X 和当前的输入符号 a 行事的。对于任何(X,a),总控程序每次都执行下述三个动做之一:

  • 若 X=a=‘ # ’,则分析成功。
  • 若 X=a≠‘ # ’,则把 X 从栈顶弹出,让 a 指向下一个输入符号。
  • 若 X 为一非终结符,则查分析 表M。若 M[X,a] 中为A—产生式,将 A 自栈顶弹出,将产生式右部符号串按逆序逐一推入栈中;当产生式为 A 时,则只将 A→ε弹出便可。若 M[X,a] 中为空,则调用出错处理程序。
例题:求预测分析表

题目(同上):

给定文法 G[S]:
(1)E -> TE'
(2)E' -> ATE'|ε
(3)T -> FT'
(4)T' -> MFT'|ε
(5)F -> (E)|i
(6)A -> +|-
(7)M -> *|/

答案:
FIRST 集,FOLLOW 集,SELECT 集以下
在这里插入图片描述
(图片来自教材:《编译原理》张晶老师版)

文法 G(E) 的预测分析表 M:
提示: 根据 SELECT 可选集对应
在这里插入图片描述
(图片来自教材:《编译原理》张晶老师版)

**用上述文法 G(E) 识别句子 i+i*i 的分析过程:**
在这里插入图片描述
(图片来自教材:《编译原理》张晶老师版)

详细分析:
(1)执行顺序:

步骤 1 为初始化,分析栈放入 # 后,放入开始符号;
根据分析栈栈首为 E,余留串首为 i,可定位到 E -> TE',逆序放入分析栈;
此时,栈首为 T,串首为 i,根据 T 和 i 定位到 T -> FT',逆序放入;
此时,栈首为 F,串首为 i,根据 F 和 i 定位到 F -> i,只有一个,直接放入;
此时,栈首为 i,串首为 i,则是识别成功,余留串中第二个,依次...

(2)再根据预测分析表 M 选出产生式,没有则调用出错处理程序
(3)注意必定要逆序入分析栈,为何要逆序放入分析栈呢?

由于是 LL(1) 分析法, LL(1) 文法是从左向右扫描,第二个L是最左推导的意思,最左推导就是每次都先推最左边的一个非终结符。LL(1) 分析法是每次拿出分析栈的栈顶,若是不逆向最左端的非终结符就会在栈中,无法拿出来继续推导。经过逆序,能够实现每次拿出栈顶,就是拿出最左非终结符,就能够实现最左推导

再回顾 LL(1) 文法名称的含义:
名称| 含义
--- | ----
第一个L | 从左到右扫描输入串
第二个L | 使用最左推导方法
1 | 只需查看一个输入符号即可决定选择哪一个产生式进行推导

(4)当分析栈栈顶元素和输入串最左端相同时,符合,分析栈栈顶出栈,识别下一个余留输入串。

(六)关于递归降低法

对于 LL(1) 文法的分析能够采用两种方法:

  • 一种是上一节介绍的 LL(1) 分析法,它利用 LL(1) 分析表和语法分析栈来实现
  • 一种是递归降低法,它利用一组可相互递归调用的子程序来实现
递归降低法基本思想

为每个非终结符编制一个子程序
子程序的名字表示一个产生式左部的非终结符
程序体是按该产生式右部的符号串顺序编写的。

每匹配一个终结符,则再读入下一个符号,对于产生式右部的每一个非终结符,则调用相应子程序。

当一个非终结符对应多个候选式时,子程序体按 SELECT 集决定选用哪一个候选式。

例题:递归降低法

题目:

给定文法 G[S]:
(1)S -> AaB|Bb
(2)A -> aD|D
(3)B -> d|e|ε
(4)D -> fD|g

答案:
FIRST 集,FOLLOW 集,SELECT 集以下
在这里插入图片描述
(图片来自教材:《编译原理》张晶老师版)

由于:

可知此文法是 LL(1) 文法

递归降低法分析:

主函数:
scan;
call S;
if token = '#' then accept
else error;

scan 表示调用词法分析程序读入下一个单词至变量 token;
error 表示报错处理。

函数 S:
if token in {a,f,g}
    then {
        call A;
        match(a);
        call B;
    }
else if token in {d,e,b}
    then {
        call B;
        match(b);
    }
else error;

match(a) 表示若当前输入单词为 a,则调用 scan,不然调用 error

上述递归降低分析器彻底是按照产生式的形式编写的,处理针对四非终结符要编写四个函数,还要有主函数。

当分析句子时,须要调用许多与文法非终结符对应的函数。

递归降低法的优势:
(1)分析器编写速度快
(2)因为分析器非紧密对应性,容易保证语法分析器的正确性,至少使得任何错误都变得简单和易于发现

递归降低法的缺点: (1)在语法分析期间高深度的递归调用影响了分析器的效率,许多时间须要花费在递归子程序之间的链接上 (2)若是瞎用的高级语言不容许递归,那么就不能使用递归降低法,能够用 LL(1) 分析法

相关文章
相关标签/搜索