跳转至

三种分析的简明理解

一、先建立直觉:LR 分析到底在干嘛?

LR 分析本质是在做一件事:

👉 从左到右读输入(L),用“最右推导的逆过程”(R)来还原句子

你可以把它想成:

📦 我一边读字符串,一边尝试“把它折叠回文法的开始符号”

比如:

id + id * id

分析器会不断做两种操作:

  • Shift(移进):读一个符号进栈
  • Reduce(归约):把栈顶的一段替换成一个非终结符

二、核心问题:什么时候可以“归约”?🤔

关键难点就在这里:

👉 看到一段符号,怎么判断“现在可以归约了”?

不同 LR 方法,本质区别就在于:

🔍 “我判断归约的依据有多聪明?”


三、三种方法逐个讲清楚


1️⃣ LR(0):最“憨”的方法

👉 完全不看后面是什么,看到能归约就归约

🔹 特点

  • 不看任何输入(0 个向前看符号)
  • 只根据当前状态决定

🔹 问题

很容易出错,比如:

E → E + T
E → T

当栈里是 T 时:

👉 到底该:

  • 归约成 E
  • 还是继续读后面(可能有 +)?

❌ LR(0) 不知道 → 冲突!


✅ 一句话总结

LR(0):盲目归约,不看未来 → 很容易冲突


2️⃣ SLR(1):加一点“常识”

👉 用 Follow 集来辅助判断

🔹 核心思想

只有当“下一个输入符号 ∈ Follow(A)”时,才允许:

A → β 进行归约

🔹 举个直觉例子

如果:

Follow(E) = { +, ), $ }

那么:
👉 只有看到这些符号,才允许把 T → E


🔹 改进点

相比 LR(0):

✅ 不再“瞎归约”
✅ 用一点“全局语法知识”(Follow 集)


🔹 但问题是…

Follow 是:

“全局的、粗糙的信息”

有时候会误判,仍然可能冲突。


✅ 一句话总结

SLR(1):用 Follow 集辅助判断,但不够精细


3️⃣ LR(1):真正聪明的版本 🧠

👉 每个项目都带一个“向前看符号”


🔹 什么是 LR(1) 项?

普通 LR(0) 项:

A → α · β

LR(1) 项:

A → α · β , a

👉 这个 a 表示:

“只有当后面是 a 时,才允许归约”


🔹 核心思想

👉 每个状态里的归约条件都是:

🎯 精确到具体上下文的

不是 Follow 集那种“模糊规则”。


🔹 优点

✅ 几乎不冲突
✅ 能处理更复杂文法


🔹 缺点

❌ 状态数爆炸(非常多)
❌ 实现复杂


✅ 一句话总结

LR(1):每个归约都带精确条件 → 最强但最复杂


四、一个超级形象的类比 🧠

把三者理解成“做决定的三种人”:

方法 类比 决策方式
LR(0) 不看情况的人 😵 “能做就做”
SLR(1) 看天气预报的人 🌦️ “大概可以”
LR(1) 实时看路况的人 🚗 “精确判断”

五、核心区别总结(非常重要)

方法 看未来? 用什么信息 精度 冲突情况
LR(0) 很低 很多
SLR(1) ✔(1个) Follow 集(全局) 中等
LR(1) ✔(1个) 每个项目的 lookahead(局部) 很高 很少

六、你可以这样记(考试/面试版)

👉 一句话记忆:

  • LR(0):不看输入,直接归约
  • SLR(1):用 Follow 集限制归约
  • LR(1):用精确 lookahead 控制归约

七、如果你想更进一步

你可以继续问我,我可以帮你深入:

  • LR(0) 项目集是怎么构造的(closure / goto)
  • SLR 为什么用 Follow(推导逻辑)
  • LR(1) 为什么状态会爆炸
  • LALR(1) 是怎么优化 LR(1) 的(实际编译器用这个)

我可以给你画图 + 手推一个完整例子 👍

解释一下句柄,活前缀等概念和以上的关系

很好,这几个概念(句柄、活前缀)其实就是 LR 分析的“灵魂”。如果你把它们搞懂了,LR(0)/SLR(1)/LR(1) 就不只是“规则”,而是一套非常自然的过程

我还是用直觉 + 小例子 + 和 LR 的关系来讲。


一、什么是“句柄”?(Handle)

🔹 直觉理解

👉 句柄 = 当前这一步“应该被归约”的那一小段

也就是:

🎯 最右推导的逆过程中的“下一步”


🔹 举个简单例子

文法:

E → E + T
E → T
T → id

输入:

id + id

分析过程(简化):

  1. 读入 id
    👉 可以归约:id → T
    👉 句柄就是:id
  2. 再归约:T → E
    👉 句柄就是:T
  3. 读入 + id
    👉 最后归约:E + T → E
    👉 句柄是:E + T

🔹 本质定义(稍微严谨一点)

在一个右句型中,
可以被一步归约的子串,就是句柄。


🔹 一句话总结

👉 句柄 = 当前最应该“折叠”的那一段


二、什么是“活前缀”?(Viable Prefix)

这个是 LR 分析最核心、最容易懵的概念之一。


🔹 直觉理解

👉 活前缀 = “已经读入的、还没出错的那部分”

换句话说:

🎯 它是“可能成为句柄左边部分”的前缀


🔹 更形象一点

你在读字符串:

id + id * id

读到一半:

id + id

👉 这个前缀如果“还可能是某个正确句子的前半部分”,
它就是一个活前缀


🔹 错误对比

比如:

  • id

👉 这就不是活前缀(开头就错了)


🔹 更本质的定义

活前缀 =
不超过某个右句型句柄右端的前缀

(这句话考试用,理解用上面的直觉)


🔹 一句话总结

👉 活前缀 = 目前为止“还合法”的栈内容


三、句柄 vs 活前缀(关键区别)

概念 是什么 位置
句柄 要归约的那一段 栈顶附近
活前缀 当前整个合法前缀 整个栈

👉 关系是:

句柄 ⊂ 活前缀


四、它们和 LR 分析的关系(重点🔥)

现在把它们和 LR(0)/SLR(1)/LR(1)连起来:


🔹 1️⃣ LR 分析器在干嘛?

👉 核心任务只有一个:

🎯 在活前缀中找到句柄


🔹 2️⃣ 栈里存的是什么?

👉 栈中内容就是:

一个“活前缀”


🔹 3️⃣ 什么时候 Reduce?

👉 当栈顶出现:

某个句柄

就执行归约


🔹 4️⃣ LR(0)/SLR(1)/LR(1) 的区别本质

还记得我们之前说的:

❓“什么时候可以归约?”

现在可以升级成更本质的说法:

方法 本质
LR(0) 猜测句柄(不看未来)
SLR(1) 用 Follow 集“粗略判断句柄”
LR(1) 精确判断句柄(带上下文)

五、再来一个完整“串起来”的理解 🌟

可以这样理解整个 LR 过程:


🧩 Step 1:读输入

不断 shift:

👉 栈变成一个活前缀


🧩 Step 2:检查栈顶

问:

❓“这里是不是一个句柄?”


🧩 Step 3:如果是句柄

👉 就 reduce


🧩 Step 4:重复

直到变成开始符号


六、一个非常关键的结论(考试爱考)

👉 LR 分析的本质:

🎯 识别活前缀,并在其中定位句柄


七、终极一句话总结(强烈建议记住)

👉 把所有概念压缩成一句话:

LR 分析器 = 在“活前缀”中识别“句柄”,并进行归约