java开发编译器:LR 状态机的缺陷与改进

来源:互联网 发布:鼠标垫什么牌子好 知乎 编辑:程序博客网 时间:2024/05/29 14:29

阅读博客的朋友可以到我的网易云课堂中,通过视频的方式查看代码的调试和执行过程:

http://study.163.com/course/courseMain.htm?courseId=1002830012

前两节我们构造的状态机有些缺陷,当我们进入某个状态节点时,根据该节点的特性,我们需要产生一些动作,根据上两节的有限状态机图,当我们进入节点5,我们发现,符号”.”为位于表达式的最右边,在 . 后面不再有其他非终结符或终结符,进入这样的节点时,我们要根据表达式做一次reduce操作,例如在节点5中,表达式和点的情况如下:

t -> f .

此时,我们要根据推导做一次reduce操作,把 f 弹出解析堆栈,把 t 压入解析堆栈,reduce 操作后,状态机的状态从节点5退回到节点0.

同理,如果状态机进入节点3,由于 . 位于推导表达式的最右边,在 . 的后面没有了其他非终结符或终结符,于是要根据节点的推导表达式做reduce操作,根据节点3的表达式:

f -> NUM.

我们需要把 NUM 弹出堆栈,同时将非终结符f压入堆栈,并从节点3 退回到节点0.

问题在于不是每一个节点都有能清晰明确的指定我们分析过程该做什么动作,一个明显的例子是节点1,该节点含有两个表达式,一个表达式中,符号点处于最末尾:

e -> t .

另一个表达式,符号 . 处于表达式的中间:

t -> t . * f

那么像这种情况,我们该做reduce 还是 shift? 这种情况我们称之为 shift / reduce 矛盾。

还有节点11,该节点含有两个表达式,并且符号 . 都在这两个表达式的最后面:

e -> e + t .
t -> t * f .

当处于该节点时,我们到底该依据哪一个表达式做reduce操作?这种情况我们称之为reduce矛盾。

这一节,我们将研究处理这种矛盾的算法。

SLR(1) 语法:

有一个处理 shift / reduce 矛盾的简单方法。还记得LL(1)语法中我们研究过的 FOLLOW set 算法吧。FOLLOW set 指的是,对某个非终结符,根据语法推导表达式构建出的所有可以跟在该非终结符后面的终结符集合,我们称作该非终结符的FOLLOW set.

对于节点4的两个表达式:

e -> t .
t -> t . * f

进入该节点后,到底应该根据第一个表达式做reduce,还是根据第二个表达式做shift操作呢。如果,当前输入字符如果属于 e 的 FOLLOW set, 那么我们就做reduce操作。
上节例子中,根据给定语法,每个非终结符的Follow set 如下:

FOLLOW(s) = {EOI}
FOLLOW(e) = {EOI, },+}
FOLLOW(t) = {EOI, }, + , * }
FOLLOW(f) = {EOI, }, +, * }

对于节点4,如果当前输入字符属于非终结符e的FOLLOW set, 那么就根据表达式:

e -> t.

做reduce 操作,要不然根据另外一个表达式做shift操作。

其他类似节点可根据相同的原则处理,如果语法构建的状态机,出现reduce / shift 矛盾的节点都可以根据上面的原则处理的话,那么这种语法,我们称之为 SLR(1) 语法,SLR 意思是 simple LR 。

LR(1) 语法

有些语法构建的状态机,某些节点的shift / reduce 矛盾可能无法根据上面给出的原则进行处理。当前输入的字符既可以出现在第一个推导表达式左边非终结符的FOLLOW set 中,同时又是第二个表达式 . 右边的终结符,例如上面的节点4,如果 * 属于 e 的 FOLLOW set, 那么shift /reduce 矛盾就难以解决了。

事实上,我们不需要考虑当前输入字符是否属于非终结符的FOLLOW set, 只要考虑,当前输入字符,当我们做了reduce操作后,在状态机当前上下文环境下,是否能合法的跟在reduce后的非终结符的后面。

这种能合法的跟在某个非终结符后面的符号集合,我们称之为look ahead set, 它是FOLLOW set 的子集。

结合具体实例,我们看看 look ahead set 是怎样的。对于状态节点 11, 它是一个 shift / reduce 矛盾 节点。从节点0到节点11的路径有两条:

0 -> 2 -> 9 -> 11
0 -> 4 -> 8 -> 9 -> 11

要收集 e 的 look ahead 集合,我们只要收集这两条路径上,跟在 e 后面的终结符就可以了。当我们做reduce操作后,相应的非终结符会被压入堆栈,一旦该非终结符被压入解析堆栈,能跟在该非终结符后面的终结符最好要对应于下即将要输入的字符。我们看节点0,如果解析过程中,产生一个生成 e 的reduce 回到节点0,例如:
(0, NUM) -> 3 –(reduce by f -> NUM.) -> 0
(0, f) -> 5 – (reduce by t -> f.) -> 0
(0, t) -> 1 – (reduce by e -> t.) -> 0

此时,在节点0以及当前输入 e 跳转到节点2。

根据表达式:

e -> e . + t

由于 . 后面跟着 +, 我们希望进入节点2 后,当前输入的字符就是 +, 或者根据另一个表达式:

s -> e .

我们也可以期望,下一个输入字符是EOI, 这样我们的
look ahead 集合就包含 EOI, +.

也就是说,如果状态机根据路径:
0 -> 2 -> 9 -> 11

进入状态节点11, 并且进入节点11后,当前输入是EOI, + 的话,我们可以做reduce操作,退回到节点0.

再看节点4中的表达式:

f -> ( . e )

跟在e 后面的终结符是 ), 这表明如果进入到节点11,并且输入是字符 ), 时,我们可以做reduce 操作,同时根据路径:0 -> 4 -> 8 -> 9 -> 11 返回到节点4,并且e也会被压入解析堆栈,这样根据 (4, e) 可以进入节点8,然后根据(8, ) ) 可以进入节点 10.

由此 e 的 look ahead 集合就包含 EOI, + , ), 在节点11,决定是否要做reduce ,只要看下一个输入的字符是否属于e的look ahead 集合就可以了。

这样一来,每个节点中的每个表达式,都需要配备一个对应的 look ahead 集合,如果两个表达式相同,但他们的look ahead 集合不同,那么,我们就认为这两个表达式也是不同的。假定一个表达式结合它对应的look ahead 如下:

s -> α .x β, C
x -> . r

C 是 look ahead 集合,α, β, r 是0个或多个终结符或非终结符的组合, x 是非终结符,那么 x -> . r 的look ahead 集合如下:

FIRST(β C),就是 FIRST(β) 并上 C.

创建带有look ahead 集合表达式的过程主要在closure阶段进行。举个例子,对于节点0中的表达式:

s -> . e

我们先给他初始化一个look ahead 集合为 {EOI}.于是有:

s -> α .x β, C
s -> .e , {EOI}
根据:
x -> . r FIRST(β C)

我们有:
e -> .e + t FIRST(β C)
e -> . t FIRST(β C)
由于β 是空,因此 FIRST(β C) = C = {EOI},于是就有:

e-> .e + t {EOI}
e-> .t {EOI}

得到上面的表达式后,我们继续往下推:
s -> α .x β, C
e-> .e + t {EOI}

根据 :
x -> .r FIRST(β C)
e -> .e + t
由于β = + t, 于是
FIRST(β) = +
FIRST(β C) = {+, EOI}

因此有新的表达式:
e -> .e + t {+, EOI}
这个过程反复进行,直到没有新的表达式可以生成为止。

我们根据节点0,将这个算法的具体实现走一遍,为了实现上面的算法,我们需要两个数据结构,一个是堆栈productionStack, 一个是集合 closureSet.

首先,我们初始化节点0的表达式,并给表达式赋予一个初始的look ahead 集合 {EOI}:

[s -> . e {EOI}]

将上面的表达式压入堆栈以及集合:
productionStack:

[s -> . e {EOI}]

closureSet:

[s -> . e {EOI}]

如果堆栈不为空,将堆栈顶部的表达式出栈,并利用上面讲到的算法构造新的表达式,此时栈顶的表达式为:

[s -> . e {EOI}]

根据:
s -> α .x β, C
s-> .e , {EOI}
x -> . r FIRST(β, C)

我们可以产生两个新的表达式:
[e -> .e + t {EOI}]
[e -> . t {EOI}]

分别将他们压入堆栈和加入集合:
productionStack:
[e -> . t {EOI}]
[e -> .e + t {EOI}]

closureSet:
[s -> . e {EOI}]
[e -> .e + t {EOI}]
[e -> . t {EOI}]

把堆栈顶部的表达式出栈:
[e -> . t {EOI}]

构造新的表达式:
[t -> .t * f {EOI}]
[t -> .f {EOI}]

将他们加入堆栈和集合:
productionStack:
[t -> .f {EOI}]
[t -> .t * f {EOI}]
[e -> .e + t {EOI}]

closureSet:
[s -> . e {EOI}]
[e -> .e + t {EOI}]
[e -> . t {EOI}]
[t -> .t * f {EOI}]
[t -> .f {EOI}]

将栈顶表达式出栈:
[t -> .f {EOI}]
同理构造新的表达式:
[f ->. ( e ) {EOI}]
[f -> . NUM {EOI}]

将新生成的表达式压入堆栈和加入集合:
productionStack:
[f -> . NUM {EOI}]
[f ->. ( e ) {EOR}]
[t -> .t * f {EOI}]
[e -> .e + t {EOI}]

closureSet:
[s -> . e {EOI}]
[e -> .e + t {EOI}]
[e -> . t {EOI}]
[t -> .t * f {EOI}]
[t -> .f {EOI}]
[f ->. ( e ) {EOR}]
[f -> . NUM {EOI}]

将堆栈顶部的表达式出栈:
[f -> . NUM {EOI}]
由于. 后面不是非终结符,所以对该表达式不做处理。继续弹出栈顶表达式:

[f ->. ( e ) {EOI}]

同理,由于.后面不是非终结符,所以对该表达式不做处理。继续弹出栈顶表达式:

[t -> .t * f {EOI}]

此时,β 对应的部分为 * f, 所以FIRST(β C) = FIRST(* f) 并上 {EOI}, 也就是{* EOI},于是我们生成新的表达式:

[t -> . t * f, {* EOI}]
[t -> . f {* EOI}]

将上面表达式压入堆栈和加入集合:

productionStack:
[t -> . f {* EOI}]
[t -> . t * f, {* EOI}]
[e -> .e + t {EOI}]

closureSet:
[s -> . e {EOI}]
[e -> .e + t {EOI}]
[e -> . t {EOI}]
[t -> .t * f {EOI}]
[t -> .f {EOI}]
[f ->. ( e ) {EOR}]
[f -> . NUM {EOI}]
[t -> . t * f, {* EOI}]
[t -> . f {* EOI}]

大家注意看,表达式
1.[t -> . t * f, {* EOI}] 和
2.[t -> .t * f {EOI}]
唯一的区别在于 look ahead 集合不同,第一个表达式的look ahead 集合比第二个表达式要大,我们称这种情况为表达式1 覆盖了表达式2,有了表达式1,表达式2就不必要了,因此我们把表达式2从集合里删除,同理表达式
[t -> .f {EOI}]
也要被删除,这样
closureSet为:
[s -> . e {EOI}]
[e -> .e + t {EOI}]
[e -> . t {EOI}]
[f ->. ( e ) {EOR}]
[f -> . NUM {EOI}]
[t -> . t * f, {* EOI}]
[t -> . f {* EOI}]

接下来继续弹出栈顶表达式:
[t -> . f {* EOI}]

生成新的表达式:

[f -> .( e ) {* EOI}]
[f -> .NUM {* EOI}]

将他们压入堆栈有:
productionStack:
[f -> .NUM {* EOI}]
[f -> .( e ) {* EOI}]
[t -> . t * f, {* EOI}]
[e -> .e + t {EOI}]

由于生成的表达式覆盖了closureSet中原有的表达式:

[f ->. ( e ) {EOR}]
[f -> . NUM {EOI}]

将上面两个表达式从集合中删除,并将新生成的表达式加入集合:

closureSet:
[s -> . e {EOI}]
[e -> .e + t {EOI}]
[e -> . t {EOI}]
[t -> . t * f, {* EOI}]
[t -> . f {* EOI}]
[f -> .( e ) {* EOI}]
[f -> .NUM {* EOI}]

此时栈顶两个表达式分别是:
[f -> .NUM {* EOI}]
[f -> .( e ) {* EOI}]

这两个表达式中,.右边不是非终结符,所以直接将他们出栈。继续弹出栈顶表达式:

[t -> . t * f, {* EOI}]

由于β 对应 * f, FIRST(β) = {},因此生成的look ahead 集合仍然是 { EOI}, 生成新的表达式为:

[t -> . t * f {* EOI}]
[t -> . f {* EOI}]

这两个表达式已经在集合中存在,所以不再加入集合。

继续弹出栈顶表达式:
[e -> .e + t {EOI}]

β 对应的是 + t, FIRST(β) = FIRST(+ t) = {+}, 于是生成新的表达式时对应的look ahead 集合是 {+ EOI},于是有:

[e -> . e + t {+ EOI}]
[e ->. t {+ EOI}]

分别将他们压入堆栈:
productionStack:
[e ->. t {+ EOI}]
[e -> . e + t {+ EOI}]

由于这两个表达式覆盖了集合中的表达式:

[e -> . t {EOI}]
[e -> .e + t {EOI}]

所以讲上面两个表达式从集合中删除,将新表达式加入集合:

closureSet:
[s -> . e {EOI}]
[t -> . t * f, {* EOI}]
[t -> . f {* EOI}]
[f -> .( e ) {* EOI}]
[f -> .NUM {* EOI}]
[e -> . e + t {+ EOI}]
[e ->. t {+ EOI}]
将栈顶表达式出栈:
[e ->. t {+ EOI}]

生成新的表达式:

[t -> . t * f {+ EOI}]
[t -> .f {+ EOI}]

将新生成的表达式压入堆栈同时加入集合:
productionStack:
[t -> .f {+ EOI}]
[t -> . t * f {+ EOI}]
[e -> . e + t {+ EOI}]

closureSet:
[s -> . e {EOI}]
[t -> . t * f, {* EOI}]
[t -> . f {* EOI}]
[f -> .( e ) {* EOI}]
[f -> .NUM {* EOI}]
[e -> . e + t {+ EOI}]
[e ->. t {+ EOI}]
[t -> . t * f {+ EOI}]
[t -> .f {+ EOI}]

弹出栈顶表达式:
[t -> .f {+ EOI}]

生成新的表达式:

[f -> . ( e ) {+ EOI}]
[f -> . NUM {+ EOI} ]
将新表达式压入堆栈和加入集合:
productionStack:
[f -> . NUM {+ EOI} ]
[f -> . ( e ) {+ EOI}]
[t -> . t * f {+ EOI}]
[e -> . e + t {+ EOI}]

closureSet:
[s -> . e {EOI}]
[t -> . t * f, {* EOI}]
[t -> . f {* EOI}]
[f -> .( e ) {* EOI}]
[f -> .NUM {* EOI}]
[e -> . e + t {+ EOI}]
[e ->. t {+ EOI}]
[t -> . t * f {+ EOI}]
[t -> .f {+ EOI}]
[f -> . ( e ) {+ EOI}]
[f -> . NUM {+ EOI} ]

由于堆栈顶部的两个表达式, . 右边都不是非终结符,因此将他们直接出栈。

继续弹出栈顶表达式:

[t -> . t * f {+ EOI}]

此时β 对应的是 * f, FIRST(β) = FIRST(* f) = {},于是新生成的表达式对应的look ahead 集合为 { + EOI}:

[t -> . t * f {* + EOI}]
[t -> . f {* + EOI}]

将表达式压入堆栈
productionStack:
[t -> . f {* + EOI}]
[t -> . t * f {* + EOI}]
[e -> . e + t {+ EOI}]

注意到,新生成的表达式:
[t -> . t * f {* + EOI}]
它覆盖集合中原有表达式:
[t -> . t * f {* EOI}]
[t -> . t * f {+ EOI}]

[t -> . f {* + EOI}]
覆盖了:
[t-> . f {* EOI}]
[t -> . f {+ EOI} ]

删除被覆盖的表达式,加入新表达式
closureSet:
[s -> . e {EOI}]
[f -> .( e ) {* EOI}]
[f -> .NUM {* EOI}]
[e -> . e + t {+ EOI}]
[e ->. t {+ EOI}]
[f -> . ( e ) {+ EOI}]
[f -> . NUM {+ EOI} ]
[t -> . t * f {* + EOI}]
[t -> . f {* + EOI}]

弹出栈顶表达式:
[t -> . f {* + EOI}]

构造新的表达式:

[f -> . ( e ) {* + EOI}]
[f -> . NUM {* + EOI}]

将他们压入堆栈:
productionStack:
[f -> . NUM {* + EOI}]
[f -> . ( e ) {* + EOI}]
[t -> . t * f {* + EOI}]
[e -> . e + t {+ EOI}]

由于表达式
[f -> . ( e ) {* + EOI}]
覆盖集合中的表达式:
[f -> .( e ) {* EOI}]
[f -> .( e ) {+ EOI}]
以及
[f -> . NUM {* + EOI}]
覆盖集合中原有表达式:
[f -> .NUM {* EOI}]
[f -> .NUM {+ EOI}]

删除被覆盖的表达式,加入新表达式后
closureSet:
[s -> . e {EOI}]
[e -> . e + t {+ EOI}]
[e ->. t {+ EOI}]
[t -> . t * f {* + EOI}]
[t -> .f {+ EOI}]
[f -> . ( e ) {* + EOI}]
[f -> . NUM {* + EOI}]

此时栈顶两表达式, . 右边的符号不是非终结符,将栈顶两个表达式出栈。

接着继续弹出栈顶表达式:
[t -> . t * f {* + EOI}]

生成表达式:
[t -> .t * f {* + EOI}]
[t -> .f {* + EOI}]
两个表达式已经在集合中存在,于是不做处理。

继续弹出栈顶表达式:

[e -> . e + t {+ EOI}]

它生成两个表达式

[e -> .e + t {+ EOI}]
[e -> . t {+ EOI}]

由于表达式都存在集合中,于是不做处理。到此堆栈为空,整个closure流程结束,此时整个closure集合为:

[s -> . e {EOI}]
[e -> . e + t {+ EOI}]
[e ->. t {+ EOI}]
[f -> . ( e ) {+ EOI}]
[t -> . t * f {* + EOI}]
[t -> . f {* + EOI}]
[f -> . ( e ) {* + EOI}]
[f -> . NUM {* + EOI}]

除了closure 这个步骤改变外,节点生成的其他步骤都保持不变。

0 0
原创粉丝点击