学习 bison 原理(三)

学习 bison 原理(三)

工作总是很多, 还要学习很多, 所以时间总是不够的. 本想好好总结多写
点的, 可是懒?还是想做别的?, 因此这里只能简单描述下了.

在(二)中已经做了不少准备性工作了, 这一步 4. 计算 LR0 状态集,
结果可能是一个非确定的(有冲突的)有限状态机. 在 编译原理 一书
对 LR0 有较详细的说明, bison 中计算 LR0 的方法也和书上是一致的.

LR0 计算状态的主入口函数为 LR0.generate_states(), 下面是主要步骤:
   1. 初始化分配计算所需空间, 初始化闭包(Closure)计算所需数据
   2. 建立初始状态(state), 遍历每个状态, 计算对每个符号的 GOTO
      函数, 将计算出的新状态加入到链表中
   3. 重复第2步, 直到扫描完成整个链表, 表示没有新的状态产生了.
      过程中创建出每个状态 shift, reduce 数据.
   4. 设置初态和终态, 将状态机补全为增广自动机.

这里关键在 generate_states() 里面的 while 大循环, 其遍历所有状态,
从 GOTO 计算出(可能的)新状态. 在编译原理龙书 P.157页 图4-33 有
给出规范 LR(0)项集族的计算, 基本和这里的算法一致. 我们仅描述下
程序中所使用的数据结构.

在 LR0 中的 LR0.core 类用于描述一个状态(state), 其主要属性为:
  number -- 编号, 每个状态给予一个从 0 开始的编号.
  accessing_symbol -- 和此状态关联的文法符号的编号.
  items[] -- 此状态的核心(core)项(item)集, 其值是到 Gram.ritem[] 的索引.
      另有属性 nitems 指项的数量.

一个项 (item) 指在文法G的一个产生式再加上一个位于它的体中某处
的点.(编译原理书上的定义). 例如一个产生式 A->XYZ 可以有四个项:
  A->.XYZ    A->X.YZ    A->XY.Z   A->XYZ.
而产生式 A->ε 只有一个项 A->.ε.

在论文1中也有关于项的更数学化的表述, 项型为 A->α.β, 其中
A->αβ 是文法 G 的一个产生式. 项在 bison 程序中恰好可以由 Gram.ritem[]
的索引唯一表示. 仔细查看 ritem[] 的结构, 就可以发现这一点, 这也是
为什么前面用 ritem[] 数组来记录每个产生式右部(rhs)的原因, 我刚开始
看到前面的代码时, 一直不太明白为何用 ritem[] 结构, 那这里为了表示项(item)
就是它的答案了.

LR0.core 类中, 属性 items[] 中, 有两点需要注意: 1. items[] 中仅保存
这个状态的核心(core)项. (所以这个结构名字叫 core?) 2. items[] 元素的值
是到 Gram.ritem[] 的索引, 且按照增序排列.

在 LR0.generate_states() 中会调用 Closure.closure() 函数以用于计算一个
core(仅含核心项的 state) 的闭包. 这个闭包概念在书上有说明, 这里不在细述,
加上后面的 new_itemsets() 函数调用, 计算的结果包括:
  1. 从 core 推导出的非 core 的项. 和 core 项构成当前正在扫描的
     这个状态(this_state)的完整项集(itemset).
  2. 从项集为每个符号 X 计算的所有 GOTO(this_state, X).
  3. 得到: 从当前状态的所有 shift; 在当前状态的所有 reduction;
     从当前状态可达的下一个状态的集合.

这里如何判定某个状态已经存在了呢? 方法是使用一个 hash 表将所有 core 对象
放在里面, 键就是这个 core 的 items[] 核心项集合. 计算 hash 的方法是将所
有项的编号加起来, 做为 hash 键. 这个方法比较简单, 而且不随项集顺序变化
而变化(加法有可交换性). hash 提供了O(1)的速度性能, 使得查找添加新的状态
足够工程上快速. 具体方法参见 get_state() 函数.

save_shifts() 函数为当前状态创建 shifts 结构, 其记录下在当前状态下的所有
移入转移(shift transmition). shifts 结构中的 shifts_arr[] 数组也是有序的,
有序的这一点的重要性在后面也会依赖到.

save_reductions() 函数为当前状态创建 reductions 结构, 其记录下在当前状态下
能够执行的所有归约.

如果在一个状态, 有多个可能的归约, 就是归约/归约(r/r)冲突. 如果有一个归约,
以及任意符号的移入, 则就是移入/归约(s/r)冲突. 当然这是指 LR0 下的, 因为 LR0
文法比较弱, 容易产生冲突. 对于这一点编译原理书上给出的如下文法例子可用于测试:
     S -> L = R | R
     L -> * R | id
     R -> L

在上述的多个步骤, 原 bison 有 print/dump 出信息以方便查看, 我也添加了一些打印
输出, 建议调试的时候, 仔细看看这些输出结构, 对于理解算法较有 帮助.

另外, 在这一步骤 4. 设置初态和终态, 将状态机补全为增广自动机. 和编译原理书上
所说直接加一个产生式 S'->S 以构成增广文法 G' 相比, bison 似乎做起来比较笨,
也许是因为早期版本? 后期版本会做得更容易理解吗?

 

转载于:https://my.oschina.net/u/232554/blog/94859

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值