Skip to content

算法Algorithm

广义 LR 与图结构栈

Generalized LR parsing · GLR parsing

以图结构栈共享冲突分支,并以打包森林保存多种语法树的LR泛化方法。

形式陈述 ​

广义LR分析(GLR)允许LR动作表的一个格包含多个动作,并同时保留这些动作产生的分析分支。它的核心数据结构是图结构栈(graph-structured stack,GSS):不为每条分支复制整个栈,而把共有栈段合并保存。

下面给出一个可直接实现和核查的基础版本:文法有限、没有 ε 产生式,且没有单位产生式环;使用未任意消解冲突的SLR或LR(1)表。空规则、可空后缀和无限歧义需要更精细的RNGLR等算法,本页不把这份基础伪算法未经修改地用于它们。

一个GSS节点为 (q,i),其中 q 是LR状态,i 是已经消费的token数。边从栈顶指向它的前驱,并标记被压入的文法符号及相应输入区间。每条从当前顶点回到 (0,0) 的路径代表一条可能的LR状态栈。

在输入位置 i、下一token为 a 时:

  1. 归约闭包:对每个当前顶点 (q,i) 以及 ACTION[q,a] 中每个 A→X1⋯Xk 归约,枚举其向后 k 条边、标签逆序匹配右部的路径。若到达 (p,j),就加入顶点 (GOTO(p,A),i) 和一条标记 A[j,i)、指向 (p,j) 的边
  2. 新边可能为已有栈顶新增归约路径,所以必须继续处理,直到该位置的顶点和边都不再变化
  3. 移进:对每个当前顶点的每个移进动作 q→aq′,创建 (q′,i+1),以标记 a[i,i+1) 的边指向旧顶点。完成所有这些移进后才进入下一位置
  4. 输入结束时,若某条可达栈路径具有accept动作则接受;若所有分支都无法继续则拒绝

同一 (q,i) 可以有多个前驱,绝不能把它们误合成一个前驱。状态与位置相同意味着未来LR动作相同,历史栈路径仍必须保存,因为不同路径的归约会弹到不同状态。

直觉

两条解析分支可能只在最近几个符号的分组上有区别,栈底却完全相同。分叉时共享栈底,随后若又到达同一状态和输入位置,就共享栈顶,把差别保留成多条前驱路径。这形成一个有向无环图:在本页无空串的模型中,每条边都指向更早的输入位置。

控制状态的共享与解析结果的共享是两回事。GSS回答“有哪些状态栈仍可继续”;共享打包解析森林回答“同一个区间有哪些语法树”。只保存GSS而丢掉归约来源,可以正确识别输入,却无法恢复全部结构。

森林用 (A,i,j) 表示非终结符 A 覆盖区间 [i,j)。不同产生式或分割点放在它下面的不同“打包分支”中;重复的子区间节点则复用。父节点保存所有分支,不会在第一次归约成功后覆盖旧分支。

例子与边界

保留两种分组,而不是替文法猜优先级 ​

取 E→E+E∣E∗E∣id,输入 id+id*id。此文法没有声明乘法优先;GLR的正确结果必须保留两种分组。

消费前三个token id+id 后,下一token是 *。此时既可按 E→E+E 归约已经完成的加法,也可移进乘号继续扩展右侧 E。两条路径分别形成

(id+id)∗id,id+(id∗id).

第一条在位置3就把 [0,3) 归约为 E;第二条把起于位置2的右侧表达式继续延伸到位置5。最终森林根 (E,0,5) 有两个分支:

根规则 子区间
E→E+E E[0,1)、+[1,2)、E[2,5)
E→E∗E E[0,3)、∗[3,4)、E[4,5)

其中 E[0,3) 是加法,E[2,5) 是乘法。三片 id 叶各只需保存一次。这张表已足以恢复两棵树;若强制只保留第一分支,就额外加入了文法没有声明的消歧策略。

基础校验实现使用SLR多动作表和上述GSS闭包,在长度不超过7的全部3280个 id,+,* token串上,与独立Earley识别器得到相同接受结果。这个有限核对验证了例子和实现,不替代任意长度输入上的正确性证明。

新边不能被“这个状态处理过了”吞掉 ​

假设顶点 (q,i) 已存在并处理过一次归约。随后另一分支给它增加一条前驱边,便可能新增一条长度为 k 的可弹出路径,落到不同的 (p,j),进而产生新GOTO状态。仅把“顶点处理过”作为永不再处理的标志,会漏掉这条解析。

正确调度以待处理的归约路径或新边所触发的组合为单位;最简单的教学实现反复重扫直到边集稳定。森林中新加的分组若不改变GSS边,也仍要保存在已有森林节点下,不能因为控制图未改变就丢弃。

空规则为何另需处理 ​

A→ε 会在同一输入位置加入边,可能让简单的“边总向更早位置”性质失效;可空后缀还使归约触发与新边到达顺序相互影响。RNGLR利用right-nulled项目并配套严密的调度处理这些情形,二叉化的BRNGLR进一步控制复杂度。

因此“GLR可以处理一般CFG”是关于完整算法家族的能力,不等于任意十几行分叉栈代码都覆盖所有空产生式、单位循环和歧义森林。使用库时应检查它具体实现的是哪一种变体。

推论与应用

正确性可由栈路径不变量说明:初始GSS只有初始栈;每条移进边对应一条合法LR移进;每条归约边对应一条真实可弹出的右部路径,所以不会凭空造出栈。反过来,每条非确定LR执行都能逐步在GSS中找到对应路径,因为算法没有丢弃冲突动作,并对新增路径完成闭包。accept因此对应至少一棵完整解析树。

只保存一份显式栈列表时,歧义可能让分支数指数增长。GSS把控制节点数限制为至多 (n+1)q,其中 q 是LR状态数;边数粗界为其平方。不过枚举归约路径仍有成本,不能从节点少直接推出总时间为二次。

对本页的朴素闭包实现,令最大产生式长度为 r、产生式数为 p,取 N=(n+1)q。每个位置至多有 q 个顶点及 qN 条向后边;一次全扫描每条规则的长度至多 r 的路径,粗计 O(pqrNr)。每轮非终止扫描至少增一节点或边,至多 O(qN) 轮;遍历 n+1 个位置得到保守界 O((n+1)pq2rNr+1)。固定文法时这是多项式,但远非最优。

经过二叉化、去重的专门GLR变体可获得固定文法下 O(n3) 的最坏时间;这不是上述朴素路径枚举自动拥有的界。输出所有独立树仍可能指数多,压缩森林和展开输出必须分别计费。

参考资料
  • Masaru Tomita, “An Efficient Context-Free Parsing Algorithm for Natural Languages”, IJCAI, 1985,§§3.1–3.3:栈列表、共享栈和GSS;§§4.1–4.2:子树共享与局部歧义打包
  • Elizabeth Scott and Adrian Johnstone, “Right Nulled GLR Parsers,” ACM TOPLAS 28(4), 2006, 577–618;作者机构出版目录
  • Elizabeth Scott, Adrian Johnstone, Giorgios R. Economopoulos, “BRNGLR: A Cubic Tomita-Style GLR Parsing Algorithm,” Acta Informatica 44, 2007, 427–461:三次上界属于该改进算法
关系图谱8 个相邻概念 · 2 类关系

拖动节点调整位置。

显示关系

显示:依赖

  1. 前置三跳
  2. 前置二跳
  3. 前置一跳
  4. 当前条目
  5. 后续一跳
  6. 后续二跳
  7. 后续三跳
文字版关系按与当前条目的最短距离分组
类型化关系