news 2026/5/26 10:25:38

从冲突到清晰:一步步构建SLR(1)分析表

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从冲突到清晰:一步步构建SLR(1)分析表

1. 理解SLR(1)分析表的核心挑战

第一次接触编译原理中的语法分析时,很多同学都会被各种分析表搞得晕头转向。我自己当年学习SLR(1)时,最困惑的就是为什么有些状态会同时出现"移进"和"归约"两种动作,这就像开车时导航突然告诉你"既可以左转也可以直行"一样让人抓狂。

SLR(1)分析表的核心价值就在于它能帮我们解决这类冲突。与LR(0)分析器相比,SLR(1)多了一个关键武器:Follow集合。这个小小的改进让它的分析能力大幅提升,可以处理更多类型的文法。举个例子,假设我们有个简单的算术表达式文法:

(0) S' → E (1) E → E + T (2) E → T (3) T → T * F (4) T → F (5) F → ( E ) (6) F → id

这个文法看起来简单,但在构造分析表时就会遇到典型的移进-归约冲突。SLR(1)的聪明之处在于,它会检查下一个输入符号是否在归约产生式左部非终结符的Follow集中,只有符合条件时才执行归约,否则就选择移进。

2. 从文法到项目集规范族

2.1 文法的拓广与项目表示

构造SLR(1)分析表的第一步是对原文法进行拓广。这就像给房子打地基,虽然看起来多此一举,但能确保分析过程有个明确的起点。拓广方法很简单:添加一个新的开始符号S'和产生式S'→S。对于上面的例子,我们已经有了(0) S'→E。

接下来要把每个产生式转换成多个"项目"。项目就是在产生式右部某个位置加个点,表示分析进度。比如E→E+T会产生三个项目:

  • E→·E+T
  • E→E·+T
  • E→E+·T
  • E→E+T·

点在最后的就是归约项目,表示这个产生式已经完整识别,可以归约了。我第一次做这个练习时,经常漏掉某些项目,后来发现一个技巧:对于右部有n个符号的产生式,它会产生n+1个项目。

2.2 构造项目集规范族

项目集规范族(Canonical Collection)是SLR(1)分析的核心数据结构,可以看作是一个DFA的状态集合。构造过程采用闭包(closure)和转移(goto)两个操作:

闭包操作就像打开一个俄罗斯套娃。从一个核心项目出发,如果点后面是非终结符,就要把这个非终结符的所有产生式也加进来(点在开头)。比如对于项目E→·E+T,因为E后面有点,所以要把E的所有产生式(E→·E+T和E→·T)都加入闭包。

转移操作则是在看到某个符号X后,把项目中所有点在X前面的项目收集起来,然后对它们求闭包。这就像在迷宫中沿着标记前进,每个符号都指引我们前往新的状态。

3. 构建活前缀DFA

3.1 识别活前缀

活前缀(Living Prefix)是指规范句型的一个前缀,它不会越过该句型的最右句柄。简单说,就是分析过程中栈里可能出现的符号串。构造DFA的过程实际上就是在识别所有可能的活前缀。

以我们的表达式文法为例,从初始项目集I0(包含S'→·E和E的闭包)开始,逐步计算所有可能的转移。这个过程需要耐心,我建议在纸上画出每个状态和转移,就像这样:

I0: S'→·E E→·E+T E→·T T→·T*F T→·F F→·(E) F→·id

从I0出发,看到E就转移到I1,看到T到I2,看到F到I3,看到(到I4,看到id到I5。每个新状态都要计算其闭包。

3.2 处理冲突状态

在构造DFA时,最需要关注的就是包含冲突的项目集。典型的冲突有两种:

  1. 移进-归约冲突:同一个符号下既可以移进又可以归约
  2. 归约-归约冲突:同一个符号下可以用不同产生式归约

比如在状态I2中:

  • E→T·
  • T→T·*F

当下一符号是*时,既可以按E→T·归约,也可以按T→T·F移进。这就是典型的移进-归约冲突。SLR(1)的解决之道是检查是否在Follow(E)中。如果不在,就选择移进;如果在,那就真的冲突了,文法就不是SLR(1)的。

4. 计算关键Follow集合

4.1 Follow集合的定义与计算

Follow(A)是所有句型中可能出现在非终结符A后面的终结符集合。计算Follow集的规则有三条:

  1. 对于开始符号S,把$(输入结束标记)加入Follow(S)
  2. 如果存在产生式A→αBβ,把First(β)的非ε元素加入Follow(B)
  3. 如果存在产生式A→αB或A→αBβ且ε∈First(β),把Follow(A)加入Follow(B)

对于我们的表达式文法,Follow集计算如下:

  • Follow(S') = {$}
  • Follow(E) = {$, +, )}
  • Follow(T) = {$, +, ), *}
  • Follow(F) = {$, +, ), *}

4.2 用Follow集解决冲突

回到状态I2的冲突,Follow(E)是{$, +, )},而不在其中。因此当下一符号是时,我们选择移进;如果是$, +或),就按E→T·归约。这样冲突就完美解决了。

这个判断过程就是SLR(1)比LR(0)强大的地方。LR(0)遇到这种情况只能报错,而SLR(1)通过Follow集的额外信息做出了合理选择。不过要注意,如果*也在Follow(E)中,那冲突就无法用SLR(1)解决,需要考虑更强的LR(1)或LALR(1)方法。

5. 填充分析表

5.1 分析表的结构

SLR(1)分析表有两个部分:动作表(action)和转移表(goto)。动作表处理终结符,包含四种操作:

  1. 移进(s):将当前符号和转移状态压栈
  2. 归约(r):按指定产生式归约
  3. 接受(acc):成功接受输入
  4. 空白:错误情况

转移表则处理非终结符,指示goto操作后的新状态。

5.2 填表步骤详解

填表过程就是遍历所有状态和符号:

  1. 对于每个移进动作,比如在状态Ii看到符号a转移到Ij,就在action[i,a]填"s j"
  2. 对于归约项目A→α·在状态Ii,对每个a∈Follow(A),在action[i,a]填"r k",k是产生式编号
  3. 对于接受项目S'→S·在状态Ii,在action[i,$]填"acc"
  4. 对于goto转移,比如在状态Ii看到A转移到Ij,就在goto[i,A]填"j"

以状态I2为例:

  • 包含E→T·和T→T·*F
  • 对于Follow(E)中的$,+,),填r2(用产生式E→T归约)
  • 对于*,填s7(移进到I7)
  • goto部分:没有新的非终结符转移

5.3 验证分析表

填完表后要仔细检查是否有冲突(同一格子有多个动作)。没有冲突才是有效的SLR(1)分析表。对于我们的表达式文法,最终的分析表如下:

状态id+*()$ETF
0s5s4123
1s6acc
2r2s7r2r2
3r4r4r4r4
......

6. 实战调试技巧

在实际构造SLR(1)分析表时,有几个容易出错的地方需要特别注意:

首先,项目集闭包的计算要完整。我经常犯的错误是漏掉间接的非终结符。比如A→·B的闭包不仅要加B的产生式,如果B→·C,还要加C的产生式。

其次,Follow集的计算要准确。特别是第三条规则(A→αB的情况),需要反复传递Follow集,直到不再变化为止。建议用不同颜色标注已经处理过的非终结符。

最后,填表时要仔细核对每个动作的来源。一个好的做法是为每个填写的动作标注理由,比如:

  • action[2,*]=s7:因为I2有T→T·F,看到移进到I7
  • action[2,+]=r2:因为I2有E→T·,+∈Follow(E)

当遇到真正的冲突(无法用SLR(1)解决)时,可能需要考虑修改文法或者使用更强大的分析器生成工具。在实践中,很多编程语言的文法都经过特别设计,使其能被SLR(1)或LALR(1)分析器处理。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/26 10:15:00

【SSD】FTL综述

1.FTL(Flash Transition Layer)作用1:完成主机的逻辑地址到的闪存的物理地址的映射主机(host) 逻辑地址(LBA) 闪存(Flash) 物理块地址(PBA) 映射(Mapping)固件(FW FirmWare)作用2:完成垃圾回收(GC Garbage Collection) 闪存块不能不能覆写,一…

作者头像 李华
网站建设 2026/5/26 10:15:00

Vue.js与D3.js融合实战:构建交互式知识图谱可视化应用

1. 为什么选择Vue.jsD3.js组合 在构建交互式知识图谱可视化应用时,技术选型往往让人纠结。我尝试过多种前端技术栈组合,最终发现Vue.js和D3.js的搭配堪称黄金组合。Vue.js的响应式数据绑定和组件化开发,恰好弥补了D3.js在DOM操作上的繁琐&…

作者头像 李华
网站建设 2026/5/26 10:14:31

VideoTogether终极指南:跨平台视频同步插件,让异地观影零距离

VideoTogether终极指南:跨平台视频同步插件,让异地观影零距离 【免费下载链接】VideoTogether Browser Extension to Sync Video Playback on All Video Platforms / 一起看视频浏览器插件,兼容所有平台 项目地址: https://gitcode.com/gh_…

作者头像 李华
网站建设 2026/5/26 10:13:16

[CTF] 从CBC模式到权限窃取:字节翻转攻击实战剖析

1. 从登录框到管理员权限:CBC字节翻转攻击全景解析 想象你正在参加一场CTF比赛,眼前是一个普通的登录页面。系统提示"只有admin能看到flag",但尝试用admin登录时却显示"admin禁止登录"。这种看似矛盾的场景背后&#xff…

作者头像 李华
网站建设 2026/5/26 10:13:11

Python运算符深度解析:从字节码到重载的底层逻辑

1. Python 运算符:不只是“ - * /”,而是程序逻辑的底层齿轮你刚学 Python 时,大概率是从print(2 3)开始的。那一刻,你没意识到自己正亲手拨动计算机最底层的逻辑开关——运算符不是语法糖,不是教学示例里的装饰品&am…

作者头像 李华