【编译原理】【C语言】实验四:预测分析法(1)构造分析表


C语言
实验环境:Visual Studio 2019
author:zoxiii


1、实验内容

  实现给定文法的预测分析表。

  • 输入:某一文法(可以使用书中的例题)
  • 输出:该文法对应的LL(1)分析表

2、前期准备

2.1 LL(1)分析法的原理

   L L ( 1 ) LL(1) LL(1)分析法又称预测分析法,是一种不带回溯的飞度贵自顶向下分析法。 L L ( 1 ) LL(1) LL(1)的含义是:第一个L表明自顶向下分析时从左至右扫描输入串的;第二个 L L L表明分析过程中将用最左推导; “ 1 ” “1” 1表明只需向右查看一个符号就可决定如何推导(即可知用哪个产生式进行推导)。
   L L ( 1 ) LL(1) LL(1)分析法的基本思想是根据输入串的当前输入符号来唯一确定选用某条规则(产生式)来进行推导;当这个输入符号与推导的第一个符号相同时,再取输入串的下一个符号,继续确定下一个推导应选的规则;如此下去,直到推导出被分析的输入串为止。

2.2 构造FIRST、FOLLOW集合的过程

  在表驱动的 L L ( 1 ) LL(1) LL(1)分析器中,除了分析表因文法的不同而异之外,分析栈、控制程序都是相同的。因此,构造一个文法的表驱动 L L ( 1 ) LL(1) LL(1)分析器实际上就是构造该文法的分析表。
  为了构造分析表 M M M,需要预先定义和构造两族与文法有关的集合 F I R S T FIRST FIRST F O L L O W FOLLOW FOLLOW
  (1) F I R S T FIRST FIRST集合的构造方法:对文法中的每一个非终结符 X X X构造 F I R S T ( X ) FIRST(X) FIRST(X),其方法是连续使用下述规则,直到每个集合的 F I R S T FIRST FIRST不再增大为止。(注:对终结符 a a a而言, F I R S T ( ‘ a ’ ) = { a } FIRST(‘a’)=\{a\} FIRST(a)={a},因而无需构造。)
  ①若有产生式 X − > a ⋅ ⋅ ⋅ X->a··· X>a,且 a ∈ V T a∈V_T aVT,则把 a a a加入到 F I R S T ( X ) FIRST(X) FIRST(X)中;若存在 X − > ε X->ε X>ε,则将 ε ε ε也加入到 F I R S T ( X ) FIRST(X) FIRST(X)中。
  ②若有 X − > Y ⋅ ⋅ ⋅ X->Y··· X>Y,且 Y ∈ V N Y∈V_N YVN,则将 F I R S T ( Y ) FIRST(Y) FIRST(Y)中的所有非 ε ε ε元素(记为 “ ∖ { ε } ” “\setminus\{ε\}” {ε})都加入到 F I R S T ( X ) FIRST(X) FIRST(X)中,若有 X − > Y 1 Y 2 ⋅ ⋅ ⋅ Y k X->Y_1Y_2···Y_k X>Y1Y2Yk,且 Y ∼ Y Y \sim Y YY都是非终结符,而 Y 1 ∼ Y i Y_1 \sim Y_i Y1Yi的候选式都有 ε ε ε存在,则把 F I R S T ( Y j ) ( j = 1 , 2 , ⋅ ⋅ ⋅ , i ) FIRST(Y_j)(j=1,2,···,i) FIRST(Yj)j=1,2,,i的所有非 ε ε ε元素加入到 F I R S T ( X ) FIRST(X) FIRST(X)中;特别是当 Y 1 ∼ Y k Y_1 \sim Y_k Y1Yk均含有 ε ε ε产生式时,应把 ε ε ε也加到 F I R S T ( X ) FIRST(X) FIRST(X)中。
  (2) F O L L O W FOLLOW FOLLOW集构造方法:对文法 G [ S ] G[S] G[S]的每个终结符 A A A构造 F O L L O W ( A ) FOLLOW(A) FOLLOW(A)的方法是连续使用下述规则,直到每个 F O L L O W FOLLOW FOLLOW集合不再增大为止。
  ①对文法开始符号 S S S,置#于 F O L L O W ( S ) FOLLOW(S) FOLLOW(S)中(由语句括号“# S S S#”中的 S S S#得到)。
  ②若有 A − > α B β A->αBβ A>αBβ α α α可为空),则将 F I R S T ( β ) ∖ { ε } FIRST(β)\setminus\{ε\} FIRST(β){ε}加入到 F O L L O W ( B ) FOLLOW(B) FOLLOW(B)中。
  ③若有 A − > α B A->αB A>αB A − > α B β A->αBβ A>αBβ,且 β = > ε β=>ε β=>ε(即 ε ∈ F I R S T ( β ) ε∈FIRST(β) εFIRST(β)),则把 F O L L O W ( A ) FOLLOW(A) FOLLOW(A)加到 F I R S T ( B ) FIRST(B) FIRST(B)中(此处的 α α α也可以为空)。
  构造 F I R S T FIRST FIRST集和 F O L L O W FOLLOW FOLLOW集的过程有可能要反复进行很多次,直到每一个非终结符的 F I R S T FIRST FIRST集和 F O L L O W FOLLOW FOLLOW集都不再增大为止。

2.3 构造LL(1)分析表的过程

   F I R S T FIRST FIRST集确定了每一个非终结符在扫描输入串时所允许遇到的输入符号及所应采用的推导产生式(该非终结符所对应的产生式中的哪一个候选式)。
   F O L L O W FOLLOW FOLLOW集是针对文法中形如“ A − > ε A->ε A>ε”这样产生式的,即在使用A的产生式进行推导时,面临输入串中哪些输入符号时则此时由一空字(即 ε ε ε)匹配而不出错;当然,此时的扫描指针仍指向当前扫描的输入符后上,并不向前推进。
  所以构造分析表 M M M的步骤如下:
  ①对文法 G [ S ] G[S] G[S]的每个产生式 A − > α A->α A>α执行以下②、③步,
  ②对每个终结符 a ∈ F I R S T ( A ) a∈FIRST(A) aFIRST(A),把 A − > α A->α A>α加入到 M [ A , a ] M[A,a] M[A,a]中,其中 α α α为含有首字符 a a a的候选式或为唯一的候选式。
  ③若 ε ∈ F I R S T ( A ) ε∈FIRST(A) εFIRST(A)(或文法中有 A − > ε A->ε A>ε的产生式)则对任何属于 F O L L O W ( A ) FOLLOW(A) FOLLOW(A)的终结符 b b b,将 A − > ε A->ε A>ε加入到 M [ A , b ] M[A,b] M[A,b]中。
  ④把所有无定义的 M [ A , a ] M[A,a] M[A,a]标记为“出错”。
  一个文法 G [ S ] G[S] G[S],若它的分析表 M M M不含多重定义入口,则称它是一个 L L ( 1 ) LL(1) LL(1)文法,它所定义的语言恰好就是它的分析表所能识别的全部句子。一个上下无关文法是 L L ( 1 ) LL(1) LL(1)文法的充分必要条件是:对每一个非终结符A的任何两个不同产生式 A − > α ∣ β A->α|β A>αβ,有下面的条件成立:
  (1) F I R S T ( α ) ∩ F I R S T ( β ) = Ф FIRST(α)∩FIRST(β)=Ф FIRST(α)FIRST(β)=Ф
  (2) 假若 β = > ε β=>ε β=>ε,则有 F I R S T ( α ) ∩ F I R S T ( A ) = Ф FIRST(α)∩FIRST(A)=Ф FIRST(α)FIRST(A)=Ф
  条件(1)意味着 A A A的每个候选式都不存在相同的首字符,由 L L ( 1 ) LL(1) LL(1)分析表的构造方法可知:它避免了在分析表的同一栏目内出现多个产生式的情况,即避免了多重入口。
  条件(2)避免了在分析表的同一栏目内出现 A − > α A->α A>α A − > ε A->ε A>ε(这同样是多重入口)的情况。

2.4 需实现的函数

  首先对实验要求实现的内容进行分析,首先确定以下要实现的函数子过程:
  (1)void First(); //求文法的 F I R S T FIRST FIRST
  (2)void Follow(); //求文法的 F O L L O W FOLLOW FOLLOW
  (3)void Print_first(); //打印文法的 F I R S T FIRST FIRST
  (4)void Print_follow(); //打印文法的 F O L L O W FOLLOW FOLLOW
  (5)void Print_table(); //打印并存储 L L 1 LL1 LL1分析表

3、分析过程

3.1 分析文法的推导过程

  选择文法 G [ V ] G[V] G[V]来构造其对应的预测分析表:

G[V]:	V -> N | N[E]
E -> V | V+E
N -> i

  由于该文法包含左递归和回溯,所以首先改写文法,对文法消除左递归和回溯,得到新的文法 G ’ [ V ] G’[V] G[V]如下:

G’[V]:	V -> NT
T -> [E] | ε
E -> VM
M -> +E | ε
N -> i

  接下来按照前面的构造 F I R S T FIRST FIRST集、 F O L L O W FOLLOW FOLLOW集的方法,对改写后的文法分析得到对应的集合如下:
  (1) F I R S T FIRST FIRST集合

FIRST(V)={i}
FIRST(T)={[}
FIRST(E)={i}
FIRST(M)={ε,+}
FIRST(N)={i}

  (2) F O L L O W FOLLOW FOLLOW集合

FOLLOW(V)={#}+FIRST(M)/{ε}+FOLLOW(E)={#,+,]}
FOLLOW(T)=FOLLOW(V)={ #,+,]}
FOLLOW(E)={]}+FOLLOW(M)={]}
FOLLOW(M)=FOLLOW(E)={]}
FOLLOW(N)=FIRST(T)/{ε}+FOLLOW(V)={#,+,],[}

  再根据 F I R S T FIRST FIRST集、 F O L L O W FOLLOW FOLLOW集构造对应的预测分析表如下:

i[]+#
VV->NT
TT->[E]T->εT->εT->ε
EE->VM
MM->εM->+E
NN->i

  对于其他的 L L ( 1 ) LL(1) LL(1)文法也是同样的分析过程,在后面的运行结果中可以验证。

3.2 主程序分析

  在理解了构造 F I R S T FIRST FIRST集、 F O L L O W FOLLOW FOLLOW集的方法之后,可以知道它们不是一次循环流程判断就可以实现的,需要不断重复运算,才能得到最终的不再增大的集合,而且我们在运算、存储的过程中不能够有重复的字符,所以我们引入map和set类型的数据来定义对应的集合变量,对于不能第一次处理的字符存储到暂时变量中,等待后续处理,由此建立了如下变量:

map<char, set<char>>first; //first集合
map<char, set<char>>follow; //follow集合
map<char, set<char>>first_temp;//暂存还需处理的first集合的字符
map<char, set<char>>follow_temp;//暂存还需处理的follow集合的字符
map<char, set<string>>string_temp;//存表达式
set<char>table; //终结符号
set<char>no_end; //非终结符
string s[10][10]; //分析表

  在此基础上,主程序的流程图如图所示:

在这里插入图片描述

图1-主程序流程图

3.3 子程序流程分析

  子程序中最重要的就是求解 F I R S T ∖ F O L L O W FIRST\setminus FOLLOW FIRSTFOLLOW集合的过程,其中求解 F I R S T FIRST FIRST集、 F O L L O W FOLLOW FOLLOW集的流程图如下图所示:

在这里插入图片描述

图2-求FIRST流程图

在这里插入图片描述

图3-求FOLLOW流程图

3.4 源代码

✅⬇️代码传送地址

3.5 运行结果

  代码是通过读取文件中已经约定好的文法进行分析,输出结果,在实验中由于“ ε ε ε”不是西文字符,所以无法识别其对应的 A S C I I ASCII ASCII码,不好判断,所以我们用“ $ ”来代替“ ε ε ε”来实现。
  由下图和前面的分析过程比较可得,代码输出的结果是正确的。
在这里插入图片描述

图4-结果截图1

  另外,对文法G[E]进行分析的结果如下图所示:
  其中: G[E]: E -> TG

G -> +TG | $
T -> FS
S -> *FS | $
F -> (E) | i

在这里插入图片描述

图5-结果截图2

4、遇到问题

  (1)使用string类型的变量来存储集合的数据,不方便处理,查阅资料后将集合变量定义为map类型,便于插入新的字符、便于查找。
  (2)再对于first_temp和follow_temp的后续处理过程中,因为map会对首非终结字符进行排序,所以也造成了用于赋值的集合并不完整,但已经被赋值了,所以只处理一次并不能得到最终正确的FIRST和FOLLOW集合,所以通过设置一定的次数,确保集合能够处理到不再增大为止,才是最终的结果。
  (3)follow集的判断过程比较复杂,在实现的过程中耗时较长,最终得到的测试结果是正确的,但对于复杂的文法难免还会有未发现的错误。

参考文献:《编译原理教程 (第4版)》 胡元义 2016

  • 5
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
对于计算机专业的学生而言,参加各类比赛能够带来多方面的益处,具体包括但不限于以下几点: 技能提升: 参与比赛促使学生深入学习和掌握计算机领域的专业知识与技能,如编程语言、算法设计、软件工程、网络安全等。 比赛通常涉及实际问题的解决,有助于将理论知识应用于实践中,增强问题解决能力。 实践经验: 大多数比赛都要求参赛者设计并实现解决方案,这提供了宝贵的动手操作机会,有助于积累项目经验。 实践经验对于计算机专业的学生尤为重要,因为雇主往往更青睐有实际项目背景的候选人。 团队合作: 许多比赛鼓励团队协作,这有助于培养学生的团队精神、沟通技巧和领导能力。 团队合作还能促进学生之间的知识共享和思维碰撞,有助于形成更全面的解决方案。 职业发展: 获奖经历可以显著增强简历的吸引力,为求职或继续深造提供有力支持。 某些比赛可能直接与企业合作,提供实习、工作机会或奖学金,为学生的职业生涯打开更多门路。 网络拓展: 比赛是结识同行业人才的好机会,可以帮助学生建立行业联系,这对于未来的职业发展非常重要。 奖金与荣誉: 许多比赛提供奖金或奖品,这不仅能给予学生经济上的奖励,还能增强其成就感和自信心。 荣誉证书或奖状可以证明学生的成就,对个人品牌建设有积极作用。 创新与研究: 参加比赛可以激发学生的创新思维,推动科研项目的开展,有时甚至能促成学术论文的发。 个人成长: 在准备和参加比赛的过程中,学生将面临压力与挑战,这有助于培养良好的心理素质和抗压能力。 自我挑战和克服困难的经历对个人成长有着深远的影响。 综上所述,参加计算机领域的比赛对于学生来说是一个全面发展的平台,不仅可以提升专业技能,还能增强团队协作、沟通、解决问题的能力,并为未来的职业生涯奠定坚实的基础。
对于计算机专业的学生而言,参加各类比赛能够带来多方面的益处,具体包括但不限于以下几点: 技能提升: 参与比赛促使学生深入学习和掌握计算机领域的专业知识与技能,如编程语言、算法设计、软件工程、网络安全等。 比赛通常涉及实际问题的解决,有助于将理论知识应用于实践中,增强问题解决能力。 实践经验: 大多数比赛都要求参赛者设计并实现解决方案,这提供了宝贵的动手操作机会,有助于积累项目经验。 实践经验对于计算机专业的学生尤为重要,因为雇主往往更青睐有实际项目背景的候选人。 团队合作: 许多比赛鼓励团队协作,这有助于培养学生的团队精神、沟通技巧和领导能力。 团队合作还能促进学生之间的知识共享和思维碰撞,有助于形成更全面的解决方案。 职业发展: 获奖经历可以显著增强简历的吸引力,为求职或继续深造提供有力支持。 某些比赛可能直接与企业合作,提供实习、工作机会或奖学金,为学生的职业生涯打开更多门路。 网络拓展: 比赛是结识同行业人才的好机会,可以帮助学生建立行业联系,这对于未来的职业发展非常重要。 奖金与荣誉: 许多比赛提供奖金或奖品,这不仅能给予学生经济上的奖励,还能增强其成就感和自信心。 荣誉证书或奖状可以证明学生的成就,对个人品牌建设有积极作用。 创新与研究: 参加比赛可以激发学生的创新思维,推动科研项目的开展,有时甚至能促成学术论文的发。 个人成长: 在准备和参加比赛的过程中,学生将面临压力与挑战,这有助于培养良好的心理素质和抗压能力。 自我挑战和克服困难的经历对个人成长有着深远的影响。 综上所述,参加计算机领域的比赛对于学生来说是一个全面发展的平台,不仅可以提升专业技能,还能增强团队协作、沟通、解决问题的能力,并为未来的职业生涯奠定坚实的基础。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zoxiii

越打赏越生长

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值