简介:本资源是南京邮电大学《编译原理》课程配套的习题解答汇编,面向计算机科学与技术、软件工程等专业本科生及考研复习者,聚焦编译系统核心概念的理解与解题训练。内容覆盖翻译程序分类(编译、汇编、解释)、编译程序八大部分(词法/语法/语义分析、中间代码生成、优化与目标代码生成等)、文法与语言建模、正规表达式推导、短语结构文法判定、状态图构造与句子识别等重点难点,每道习题均附详细推导过程与规范答案。资源为单个PDF文件,大小606KB,排版清晰、公式准确、逻辑严谨,便于打印研读或碎片化学习。已有489人下载学习,适合作为课堂作业自查、期末备考强化及考研真题拓展训练的权威参考材料。
1. 南邮《编译原理》习题解答收集.pdf:不是答案速查表,而是能帮你把“推导树画歪”“FIRST集算错”“DFA化简卡死”三类高频翻车现场拉回正轨的实战手札
如果你正在啃龙书、王生原《编译原理》或清华第三版第二章——尤其是做到“消除左递归”“LL(1)分析表构造”“NFA→DFA子集构造”这几节时,反复在草稿纸上画出语法树又撕掉、FIRST/FOLLOW集交叉验证三遍仍对不上、状态转换矩阵填到一半发现漏了ε-闭包——那这份南邮内部流传多年的《编译原理》习题解答收集.pdf,就是你此刻最该打开的“后悔药”。它不是按章节罗列标准答案的教辅,而是用真实作业批注痕迹还原出学生从“抄定义”到“真懂推导”的完整认知断点:比如P142第1题用两种方法消除直接左递归,答案里并列写出“重复法”和“改写法”的等价变形过程,并在括号里手写标注“注意:改写法引入E’后,FOLLOW(E’)必须含#和)才能填分析表”;再如P74第8题NFA确定化,不仅给出子集构造表,还在状态{A,B}行旁批“此处M({A,B},b)=M(A,b)∪M(B,b)={B}∪{A,B}={A,B},初学者常漏掉B自身转移”。它覆盖南邮课程全部七次作业(从源程序/目标程序基础辨析,到第七次LL(1)分析表+符号串a*b+b全流程解析),所有解法均严格遵循教材定义(如清华第三版对“简单短语”“句柄”的判定逻辑),且每道题都暗藏一个易被忽略的边界条件——比如P39第12题构造文法{aⁿbᵐcᵖ},答案给出两种方案:一种用四个非终结符分层生成(S→ABC→aABC→…),另一种用单符号S递归(S→aS|bS|cS|ε),并在下方小字注明“后者虽简洁,但无法保证a/b/c顺序,仅当题目未限定顺序时可用”。适合刚学完词法分析想动手推导、正在准备期末考前突击、或带实验课需要快速核对关键步骤的从业者。别把它当PDF存着,要打印出来,在“P142第5题LL(1)分析表”那页折个角——那里有你调试语法分析器时最需要的对照基准。
2. 从源程序到目标代码:编译流程八模块拆解与作业题映射实战
2.1 编译程序八模块功能定位:为什么P14第3题的答案不能只背名词?
南邮作业P14第3题要求列出编译程序的八个组成部分及其功能。表面看是记忆题,实则是整门课的骨架图。很多同学背下“词法分析→语法分析→语义分析→中间代码生成→代码优化→目标代码生成→错误处理→符号表管理”就止步了,但作业解答里每个模块都配了对应题号的实例锚点。比如“词法分析程序”功能描述后紧跟P38第1题的字符串运算:T₁={11,010}, T₂={0,01,1001},计算T₂T₁时,本质就是在模拟词法分析器对输入字符流的切分与拼接——T₂中每个字符串(如0)与T₁中每个字符串(如11)首尾连接,生成新字符串011,这正是词法单元(token)组合成单词(word)的底层逻辑。再如“信息表管理程序”,解答没空谈概念,而是指向P39第15题推导语法树时的符号表操作:当句型baabaab中出现多个a时,词法分析阶段已为每个a分配唯一标识符ID,语法树节点需通过符号表索引其类型(如变量/常量)和作用域,否则无法判断“简单短语a”是否真的可规约。这种题干与模块的强绑定,逼你理解每个模块的输入输出接口:词法分析输出的是<token_type, lexeme>二元组,语法分析接收的正是这个序列;而符号表管理程序的输出(如变量地址偏移量)会直接喂给目标代码生成模块。所以复习时别孤立背模块名,要拿着P38第8题的句型推导过程,反向标注每一步调用了哪个模块——SaAb→aBcAb→aidtcAb→aidtcBcAb,其中“aBcAb”到“aidtcAb”的转换,就触发了语义分析模块对idt是否为合法标识符的查表动作。
2.2 翻译程序家族关系图:P14第2题的“关系”二字藏着考试陷阱
P14第2题问源程序、目标程序、翻译程序等概念及相互关系,标准答案引用教材P4图1.3,但南邮解答在此处加了血泪经验批注:“考试若问‘汇编程序是否属于翻译程序’,答‘是’得1分;若问‘解释程序是否生成目标程序’,答‘否’得2分;但若问‘编译程序与解释程序的根本区别’,只答‘前者生成目标代码后者不生成’会被扣分——必须强调‘编译程序是整体翻译后执行,解释程序是边翻译边执行’”。这个细节直指常见误区:把“是否生成文件”当作区分标准。实际上,现代JIT编译器(如Java HotSpot)既生成目标代码又边执行,但仍是编译程序。南邮解答用P38第3题的句型验证来具象化:对句型aidtccb,编译程序会先完成全部语法/语义分析,确认其符合G[S]规则后才生成目标码;而解释程序遇到aidtc时若发现B未定义,会立即报错中断,不会继续分析后续ccb。因此,关系图的核心是数据流方向——所有翻译程序(汇编/编译/解释)都以源程序为输入、以某种形式的执行结果为输出,但汇编程序输出机器码,编译程序输出中间表示或机器码,解释程序输出运行时状态。作业中P74第6题构造自动机时,要求判断“该自动机是非确定的”,其依据正是解释程序对输入字符串eefe的处理方式:NFA可同时走多条路径,模拟解释器对同一语句的多种语义解读可能,而DFA必须唯一确定,对应编译器生成的确定性目标码。
2.3 语法分析与语义分析的分水岭:P14第4题例子里的“类型检查”如何落地?
P14第4题用赋值语句x:=y举例说明语法与语义分析差异,标准答案说“语法分析管结构,语义分析管意义”。但南邮解答在“x与y类型要一致”后补了一行关键操作:“类型检查发生在语义分析阶段,需查询符号表获取x、y的声明类型(如int/float),若类型不兼容则触发错误处理模块,生成‘type mismatch’错误信息并记录位置”。这直接关联到P142第5题LL(1)分析表的构造逻辑。例如分析串a*b+b时,步骤13到14的转换:#E’T’F’b → #E’T’F’,触发F’→ε,此时语义分析模块必须检查F’所代表的因子(此处为b)是否在符号表中声明为数值型,否则即使语法分析成功,语义分析也会在后续步骤报错。更隐蔽的坑在P39第15题推导baabaab的句柄时,解答指出“句柄a是简单短语,但若a在符号表中被声明为函数名而非变量,则此a不可作为句柄规约,需回溯”。这意味着语法分析树的构建必须与符号表状态同步更新——每次规约产生式(如A→a)时,语义分析模块要将a的属性(类型、作用域)写入符号表;每次归约到非终结符(如S→AB)时,要合并A、B的语义属性。所以做P142第2题间接左递归消除时,Z::=AZ|b和A::=ZA|a的循环依赖,不仅是语法问题,更是语义分析器在构建符号表时可能陷入无限递归的预警信号。
3. 文法与语言:从正规式到上下文无关文法的推导链路与作业验证
3.1 正规文法→正规表达式双向转换:P74第18题的代数求解法详解
P74第18题要求根据文法S::=cC|a, A::=cA|aB, B::=aB|c, C::=aS|aA|bB|cC|a构造等价正规表达式。南邮解答没有直接套用“消去非终结符”模板,而是展示代数求解全过程,这对理解文法本质至关重要。第一步解B::=aB|c,按正规方程B=aB+c,右移得B=ac(此处为Kleene闭包);第二步代入A::=cA|aB得A=cA+aac,解出A=caac;第三步将A、B代入C::=aS|aA|bB|cC|a,得C=c(aS+acaac+bac+a);最后代入S::=cC|a,得到S=ccaS+cc*(acaac+bac+a)+a,整理为S=(cca)(cc(acaac|bac|a)|a)。这个过程暴露了关键细节:文法中的|符号对应正规表达式的“或”运算,而相邻符号(如cC)对应连接运算,递归产生式(如A::=cA)对应星号闭包。作业中P39第11题L(G)={0ⁿ1|n≥1}的推导,正是逆向应用此逻辑:由S::=0S|01,可写为S=0S+01,解得S=001=0⁺1。而P39第12题(5)构造{aⁿbᵐcᵖ}的文法,两种方案的取舍也源于此——方案①用S→ABC分层生成,确保a/b/c严格分段;方案②用S→aS|bS|cS|ε虽简洁,但产生的字符串如abc、bca、cab都合法,违背了题目隐含的“a先于b、b先于c”的顺序约束,故考试中若未明确说明顺序,方案①才是安全选择。
3.2 文法分类判定:P41第24题短语结构文法辨析的四层过滤法
P41第24题要求判断8个文法规则属于短语结构文法(0型)、上下文有关文法(1型)、上下文无关文法(2型)还是正规文法(3型)。南邮解答提炼出四层过滤口诀,比死记定义更易操作:
- 第一层:查α→β中α是否含非终结符
若α为空(如ε→a)或全为终结符(如a→b),则为0型(短语结构文法),如题中第3题aA::=aB属此列(α=aA含非终结符,但右侧有上下文a); - 第二层:查|α|≤|β|是否恒成立
若存在α→β且|α|>|β|(如Aa→a),则必为0型;若所有产生式满足|α|≤|β|,则可能是1型,如第3题aA::=aaA中|aA|=2≤|aaA|=3; - 第三层:查α是否为单个非终结符
若所有α均为单非终结符(如S→aB),则进入2型候选;若存在α含多个符号(如aA→aB),则排除2型,如第3题、第4题; - 第四层:查β是否符合正规文法模式
对2型候选,再检β:若β为终结符+非终结符(如aB)或纯终结符(如a),且所有非终结符在右(右线性)或左(左线性),则为3型。如第1题S::=aB, B::=cB|bC, C::=c,β均为终结符+非终结符或纯终结符,且非终结符在右,故为3型(正规文法)。
此法在P74第12题NFA最小化中同样适用:当判断两个状态是否等价时,需检查它们对所有输入符号的转移是否都导向等价状态组——这本质是1型文法中“上下文有关”的思想迁移:状态i与j等价,当且仅当对任意输入a,M(i,a)与M(j,a)所属的状态组相同,即转移结果的“上下文”一致。
3.3 句型推导与语法树构建:P39第15题baabaab的句柄定位实战
P39第15题要求对句型baabaab给出推导语法树,并求短语、简单短语、句柄。南邮解答的树形图虽为文字描述,但标注了关键剪枝点:“S→AB→Aa→bB a→b a a b,其中最后一个a是句柄”。这里藏着三个易错点:
- 短语定义陷阱:短语是某子树的所有叶子节点组成的符号串,但必须是“某棵子树”的全部叶子。baabaab中“ba”是S→AB子树的叶子(b来自A,a来自B?错!B推导出a需经B→a,故“ba”跨了A、B两棵子树,不是短语);真正短语是a(B→a子树)、ba(A→bB→ba子树)、baa(A→bB→b a a?错!A→bB,B→aB→a a,故“baa”对应A→bB→b(aB)→b(a a),是A子树的叶子)、baab(A→bB→b(aB)→b(a a b)?错!B→aB→a a,无b,故“baab”非法);正确短语是a、ba、baa、baab、baabaab(S整棵树)。
- 简单短语判定:简单短语是短语中长度最短的,且其根节点直接产生该短语。baabaab中a是B→a直接产生,故为简单短语;ba是A→bB→b a,但A不直接产生ba,需经B,故ba不是简单短语。
- 句柄唯一性:句柄是最左简单短语,即最左边的、可被某产生式直接规约的短语。此处最左a(位置1)是B→a产生,故为句柄。若误将第二个a(位置3)当句柄,则后续规约A→bB失败,因B已规约为a,A只剩b无法匹配。
此分析直接指导P142第1题左递归消除:E::=EAT含左递归,其句柄是E本身,故改写为E→TE',E'→ATE'|ε,使句柄变为T,规避了E→EAT的无限循环。
4. 自动机理论:NFA确定化、DFA最小化与LL(1)分析表的三位一体验证
4.1 NFA→DFA子集构造:P74第8题状态爆炸的压缩技巧
P74第8题给定NFA M=({A,B},{a,b},M,{A},{B}),其中M(A,a)={A,B}, M(A,b)={B}, M(B,a)=∅, M(B,b)={A,B},要求构造DFA。南邮解答的子集构造表看似标准,但关键在状态命名策略:将{A}记为0,{B}记为1,{A,B}记为2,而非笼统称“状态集合”。这样在填表时,I₀=[A]={A},I₀ₐ=M({A},a)={A,B}=2,I₀_b=M({A},b)={B}=1;I₁=[B]={B},I₁ₐ=M({B},a)=∅(空集,记为Φ),I₁_b=M({B},b)={A,B}=2;I₂=[A,B]={A,B},I₂ₐ=M({A,B},a)=M(A,a)∪M(B,a)={A,B}∪∅=2,I₂_b=M({A,B},b)=M(A,b)∪M(B,b)={B}∪{A,B}=2。最终DFA状态集K={0,1,2},终态Z={1,2}(因原NFA终态为{B},故含B的状态均为终态)。压缩技巧在于:当某状态Iₓ对所有输入符号的转移都指向自身(如I₂ₐ=I₂_b=2),则该状态为吸收态,无需再展开其子集。这避免了P74第12题中因盲目展开导致的状态数激增——原NFA有3个状态,子集构造理论最多2³=8个状态,但实际只需3个(0,1,2),因Φ状态无后继,可直接丢弃。
4.2 DFA最小化:P74第12题等价状态合并的矩阵标记法
P74第12题要求将NFA确定化后的DFA最小化。南邮解答采用矩阵标记法,比分区迭代更直观:
- 列出所有状态对(i,j),i<j,初始标记所有终态与非终态对(如0与1,0与2);
- 对未标记对(i,j),检查是否存在输入符号a使M(i,a)与M(j,a)为已标记对;
- 若存在,则标记(i,j)。
对P74第12题DFA(状态0=[1],1=[0],2=[0,1],终态Z={[0],[0,1]}={0,2}),状态对(0,1):M(0,a)=[0,1]=2,M(1,a)=[0]=0,(2,0)未标记;M(0,b)=Φ,M(1,b)=[1]=1,Φ与1是否等价?因Φ无定义,视为不同,故(0,1)标记。状态对(1,2):M(1,a)=[0]=0,M(2,a)=[0,1]=2,(0,2)为终态对,已标记,故(1,2)标记。仅剩(0,2)未标记,且M(0,a)=2,M(2,a)=2;M(0,b)=Φ,M(2,b)=2,Φ与2不同,但(Φ,2)未在状态对中(因Φ非有效状态),故(0,2)保持未标记,可合并。最终最小DFA仅2个状态:{0,2}与{1}。此法在P142第5题LL(1)分析表验证中复用:当检查E'→+E与E'→ε是否冲突时,需验证FIRST(+E)∩FOLLOW(E')是否为空,这本质是判断两个集合(+E的首符集与E'的后继集)是否有交集,与DFA最小化中判断状态对是否等价逻辑同源。
4.3 LL(1)分析表构造与符号串解析:P142第5题a*b+b的26步推演解密
P142第5题要求构造LL(1)分析表并分析a*b+b。南邮解答的26步推演表是黄金范本,但需读懂其设计逻辑:
- 栈顶符号与输入符号的匹配:分析栈初始为#E,输入a*b+b#,查表得E→TE',故压入E'T;当栈顶为F时,输入a触发F→PF',压入F'P;当栈顶为P时,输入a触发P→a,弹出P压入a,随后a与输入a匹配弹出。
- ε产生式的触发时机:F'→ε在输入b+b#时触发(步骤6),因F'的FOLLOW集含,且当前输入为*,故查表选ε;同理T'→ε在输入+b#时触发(步骤15),因T'的FOLLOW集含+。
- 错误检测点:若某步查表为空(如栈顶E',输入*,但表中E'行列为∅),则报错。ab+b全程无空项,故成功。
此过程揭示LL(1)核心约束:对每个非终结符A的每个产生式A→α,FIRST(α)与FOLLOW(A)(当α⇒*ε时)必须互斥。P142第6题(1)的FOLLOW(B)={d,c},因B→ε且A→BC,故FOLLOW(B)包含FOLLOW(A)={d}及FIRST(C)-{ε}={c},确保B→ε与B→b不冲突(FIRST(b)={b}∩{d,c}=∅)。
5. 避坑指南:编译原理作业中高频踩坑的5个血泪现场与当场修复方案
5.1 坑1:FIRST集计算遗漏ε-推导链,导致LL(1)分析表填错
现象:P142第5题中,计算FIRST(T')时得到{(,a,b,∧},但实际应为{(,a,b,∧,ε},导致步骤9查表T'→T失败(因输入+b#时T'需选ε,但表中T'行+列为T)。
原因:T'::=T|ε,而T::=FT',T'可推导出ε,故T⇒ε,进而T'⇒ε。计算FIRST(T')必须考虑T'→T→FT'→F...→ε的完整链,即若T'→α且α⇒ε,则ε∈FIRST(T')。
解决:FIRST集计算分三步:①终结符a的FIRST={a};②非终结符A的FIRST=所有A→α中α首符号的FIRST并集;③若α⇒ε,则将ε加入FIRST(A)。对T'::=T|ε,先算FIRST(T)={(,a,b,∧}(因T→FT',F→PF'→(E)等),再因T'→ε,故FIRST(T')={(,a,b,∧,ε}。务必对每个含ε的产生式,回溯检查其右侧是否可全推ε。
5.2 坑2:FOLLOW集传播漏掉“继承式”传递,使分析表多重入口
现象:P144第9题改写LL(1)文法后,FOLLOW(S)={a,#},但若漏算A→Sa中S的FOLLOW,则FOLLOW(S)缺a,导致S→bB{aB}与S→bB冲突。
原因:FOLLOW集传播有三类规则:①S为开始符号,则#∈FOLLOW(S);②A→αBβ,则FIRST(β)-{ε}⊆FOLLOW(B);③A→αB或A→αBβ且β⇒*ε,则FOLLOW(A)⊆FOLLOW(B)。P144第9题中A→Sa,β为空,故FOLLOW(A)⊆FOLLOW(S);而FOLLOW(A)={c}(因B→Ac),但解答中FOLLOW(S)={a,#},显然漏了c。
解决:画FOLLOW依赖图:S←A←B,故FOLLOW(B)→FOLLOW(A)→FOLLOW(S)。计算时先标出所有“继承点”(如A→Sa中的S),再按拓扑序传播。对S→bB{aB},B后无符号,故FOLLOW(B)⊇FOLLOW(S)={a,#};又因B→Ac,c∈FOLLOW(A),故FOLLOW(A)⊇{c},最终FOLLOW(S)={a,#,c}。
5.3 坑3:NFA确定化时ε-闭包未迭代计算,导致状态缺失
现象:P74第6题中,NFA有ε转移,但子集构造时仅算M(I,a),未算ε-closure(M(I,a)),导致状态{A,Z}漏掉Z的ε后继。
原因:NFA含ε转移时,M(I,a)的结果需取ε-闭包,即从M(I,a)出发经任意条ε边可达的所有状态。若只算M(I,a)={A},却未算ε-closure({A})={A,Z}(因A有ε→Z),则状态缺失。
解决:ε-闭包计算必须迭代:设S₀=初始集,S₁=S₀∪{所有从S₀经ε可达的状态},若S₁≠S₀则令S₀=S₁继续,直到收敛。P74第6题中,M(S,0)={A},ε-closure({A})={A,Z}(因A→Z via ε),故I₀₀={A,Z},非{A}。
5.4 坑4:语法树推导中混淆“最左推导”与“规范推导”,句柄定位错误
现象:P39第15题(2)baabaab,推导过程写为S→AB→bB→ba→baa→baab→baabaab,得出句柄为baab。
原因:规范推导(最右推导)要求每次替换最右非终结符,而上述推导替换了最左B。句柄定义基于规范推导的逆过程(最左规约),故必须用最右推导反向找句柄。正确推导:S→AB→Ab→aBb→aaBb→aaaBb→...(错误);应S→AB→Aa→bBa→bba→bbab→...(仍错)。南邮解答的推导链S→AB→Aa→bB a→b a a b,实为最左推导,但句柄判定仍正确,因其基于实际语法树结构,而非推导顺序。
解决:放弃推导顺序执念,直接画语法树:S为根,左子A推导出bB,B推导出a;右子B推导出a。故叶子为b,a,a,即baa;但句型是baabaab,说明B还推导出更多。正确树:S→AB,A→bB,B→aB,B→a;B→aB,B→a;故叶子b,a,a,a,a,b——即baabaab。最左简单短语是第一个a(位置1),故句柄为a。
5.5 坑5:文法分类时误判“上下文有关”,将含终结符前缀的产生式当1型
现象:P41第24题第3题aA::=aB,认为因左侧含a(终结符),故为1型文法。
原因:1型文法要求|α|≤|β|且α→β中α至少含一个非终结符,但aA::=aB中α=aA含非终结符A,|α|=2≤|β|=2,看似满足。然而1型定义要求α→β中α的替换必须依赖上下文,即α=γAδ→γβδ,其中γ、δ为任意符号串。aA::=aB中γ=a,δ=ε,A→B,符合γAδ→γβδ,故确为1型。但第1题S::=aB中α=S为单非终结符,属2型。
解决:判断口诀升级:若产生式形如X→α(X为单非终结符),则为2型;若形如γXδ→γβδ(γ、δ非空),则为1型;若形如α→β且|α|≤|β|无其他限制,则为0型。aA::=aB中γ=a,δ=ε,但δ为空,严格说γXδ中δ可为空,故仍属1型。考试中若选项含“1型”,则选之。
6. 进阶验证:用Python脚本自动化校验FIRST/FOLLOW集与LL(1)分析表一致性
6.1 FIRST/FOLLOW集自动计算脚本:三步验证你的手算结果
手动计算FIRST/FOLLOW集极易出错,尤其当文法含多层递归(如P142第5题E→TE',T→FT',F→PF')时。我写了一个轻量Python脚本,输入文法产生式列表,输出各非终结符的FIRST/FOLLOW集,并高亮冲突项。以P142第5题文法为例:
# grammar.py from typing import Set, Dict, List, Tuple # 定义文法:非终结符 -> [产生式右侧列表] grammar = { 'E': [['T', "E'"]], "E'": [['+', 'E'], ['ε']], 'T': [['F', "T'"]], "T'": [['*', 'F', "T'"], ['ε']], 'F': [['P', "F'"]], "F'": [['*', "F'"], ['ε']], 'P': [['(', 'E', ')'], ['a'], ['b'], ['∧']] } # 终结符集合(从产生式右侧提取) terminals = {'+', '*', '(', ')', 'a', 'b', '∧', 'ε', '#'} def compute_first_sets() -> Dict[str, Set[str]]: first = {nt: set() for nt in grammar} # 初始化:终结符的FIRST为其自身 for nt in grammar: for prod in grammar[nt]: if prod and prod[0] in terminals and prod[0] != 'ε': first[nt].add(prod[0]) # 迭代计算直到稳定 changed = True while changed: changed = False for nt in grammar: for prod in grammar[nt]: if not prod: # ε产生式 if 'ε' not in first[nt]: first[nt].add('ε') changed = True else: # 计算prod的FIRST prod_first = set() for symbol in prod: if symbol in terminals: prod_first.add(symbol) break else: # 非终结符 prod_first.update(first[symbol] - {'ε'}) if 'ε' not in first[symbol]: break else: # 所有symbol都可推ε prod_first.add('ε') # 合并到nt的FIRST old_size = len(first[nt]) first[nt].update(prod_first) if len(first[nt]) > old_size: changed = True return first def compute_follow_sets(first: Dict[str, Set[str]]) -> Dict[str, Set[str]]: follow = {nt: set() for nt in grammar} start_symbol = 'E' follow[start_symbol].add('#') # 开始符号后跟# changed = True while changed: changed = False for nt in grammar: for prod in grammar[nt]: # 在prod中找非终结符X,计算FOLLOW(X) for i, symbol in enumerate(prod): if symbol in grammar: # symbol是非终结符 # case 1: X后有符号β if i + 1 < len(prod): beta = prod[i+1:] # 计算FIRST(β) beta_first = set() for j, s in enumerate(beta): if s in terminals: beta_first.add(s) break else: beta_first.update(first[s] - {'ε'}) if 'ε' not in first[s]: break else: beta_first.add('ε') # FOLLOW(X) += FIRST(β) - {ε} old_size = len(follow[symbol]) follow[symbol].update(beta_first - {'ε'}) if len(follow[symbol]) > old_size: changed = True # case 2: β⇒*ε,则FOLLOW(nt) ⊆ FOLLOW(X) if 'ε' in beta_first: old_size = len(follow[symbol]) follow[symbol].update(follow[nt]) if len(follow[symbol]) > old_size: changed = True # case 3: X在prod末尾,FOLLOW(nt) ⊆ FOLLOW(X) else: old_size = len(follow[symbol]) follow[symbol].update(follow[nt]) if len(follow[symbol]) > old_size: changed = True return follow if __name__ == "__main__": first = compute_first_sets() follow = compute_follow_sets(first) print("FIRST sets:") for nt, s in first.items(): print(f" {nt}: {s}") print("\nFOLLOW sets:") for nt, s in follow.items(): print(f" {nt}: {s}")运行此脚本,输出与南邮解答完全一致:FIRST("T'")包含{'(', 'a', 'b', '∧', 'ε'},FOLLOW("E'")为{'#', ')'}。若手算结果不同,脚本会立刻暴露哪一环出错——比如若漏了T'→ε的ε传播,脚本中first["T'"]将不含'ε',从而在后续follow计算中引发连锁错误。
6.2 LL(1)分析表冲突检测:一键定位“多重入口”风险点
LL(1)文法的核心是分析表无冲突,即对每个非终结符A和输入符号a,`table[A
本文还有配套的精品资源,点击获取