编译原理 自顶向下ll1 分析

最近学了这玩意 顺便写了一个ll1的编译器 就具体说一下到底怎么编写的 

通俗的说一个例子 

1.A=>Ab

2.A=>a

 这是两个规则,其中前面的大写A表示非终结符,可以推导出右边的式子。也就是如果有一个A,你可以将其变为Ab又或者为ab。

假设上述两个是规则的情况下如果出现一个字符串ab 那么我们怎么用自顶向下的方法分析呢?

这里我们需要用到pda 简单来说就是用到栈 

首先我们将A推入栈中 

为什么要将A放入?因为A是开始的符号。所有的句子都需要从开始符号开始推导。这是我们人为定义的。

此时的栈中就只有A      (A)

因为A是非终结符,所谓非终结符就是规则的左边,因为可以继续推导,所以叫做非终结,所谓终结符就是不能推导,例如a,你无法再左侧找到他。A是非终结符,而所有的句子都是终结符组成,所以你不能进行匹配。无法匹配的情况下则自然需要进行继续推导,我们假设现在有一个表。这个表如何做成我们稍后再说。

然后我们将A取出 选择规则1 A=>Ab 

然后将Ab推入栈中 变成(Ab)

然后我们继续把A取出 此时栈中只剩(b)

然后我们用第二条规则 将A转换成a 然后放回栈中

此时栈中的情况就是(ab) 很明显 a是终结符, 既然是终结符那么就可以进行匹配。所以我们将a取出,然后匹配句子中的a, 此时句子中就是(b),而栈中也只剩下了(b) 。句子的存放可以放到队列里面。因为句子是只出不进的,而推导符号是可以进出的,并且需要将推导的结果放到最前面,所以推导只能用栈。

所以我们需要创建一个stack专门存放推导过程,一个queue存放输入的句子。

那么将刚才的过程简单的说一遍, (A,ab,),这括号第一个参数为推导的栈,第二个参数为传入进来的句子,第三个记录使用的规则序号。

(A,ab,)

(Ab,ab,1)

(ab,ab,12)

(b,b,12)

(,,12)

这便是解析的过程,那么现在的问题是什么,大家也能看出来了,为什么?为什么要使用1规则而非2规则,这背后便是表的建立。

首先我们需要进行一系列的规则的改变,

A=>Aa

A=>a

B=>C

C=>Ba

上述4个规则我们发现A=>Aa可能出现这么一种情况,A=>Aa,然后Aa=>AAa,然后不断的重复,且这个是first follow集合无法解决的问题,什么是first集? 例如A=>a A=>b这两个规则,此时进入了一个a,明眼人会选择第一个规则,为什么?这便是所谓的first集合的作用,判断现在的符号是应该选择哪个规则,也就是规则右边的第一个终结符

1.S=>A

2.A=>a

3.A=>b

S的first集合是A的first集合,规则2的first集合是a,规则3的first集合是b,而规则2和规则3都属于A,而规则1的first集合便是A,所以S的first集合便是a和b。那么当有a或者b的时候,我们就可以利用规则1转化为A然后再用23的规则转化为a或者b。

那么什么事follow集合呢?follow集合的意思便是规则后面出现的第一个终结符,这又是什么意思呢?

1.S=>Ac

2.A=>空

3.A=>b

此时你的栈上出现了符号Ab,下一个字符是b,请问一下,此时你应该选择规则2还是3?你会说规则3,为什么?因为规则2的后面跟的是什么?是c,为什么是c呢,为什么不是b呢,因为follow集合,即规则之后可能出现的值。这就能解决大部分情况的问题,即只看第一个字符就能决定此时运用哪些规则。

例如我们现在有一个c,开始符号A,规则为下

A=>ab

A=>空

A=>Bc

B=>空

那么根据first集合我们知道规则1是不适合的,那么规则2和规则3选哪一个呢?我们根据follow集合知道如果选择规则2,那么后面的follow集合就是什么都没有,而规则3是否可行呢?答案是可行,为什么?我们利用first集合和follow集合建立一个lookahead集合。也就是向前看一个字符,规则4的first集合为空,但是他的follow集合是c,也就是说first(Bc)的集合便是c,因为B为空,所以规则3的first集便是c,而既然first集是c,那么就可以用规则3进行推导。

但是左递归的出现便带来了问题,

A=>Aa

A=>a

第一个规则的first集合为A,而A的first集合为a,第一个和第二个的first集合完全相同。请问你怎么选?无法进行选择。那么此时你就只能进行尝试,但是问题又来了。我尝试Aa,之后又遇到了相同的问题,第一个字符还是a,那么继续进行尝试,此时得到了AAa,就这样会陷入无限循环。

那么我们怎么消除这种直接的左递归呢?

我们仔细想一想,Aa的意思是什么?Aa的意思无非是要么变成Aaa,要么是变成aa。那么我们如果仔细想一下,首先A最后一定是会变成终结符a,所以我们直接合并两个规则,因为第一个符号一定是a,

A=>a? 

后面的应该是什么呢?其实不难发现,后面要么为空,也就是第二个规则,要么是无数个a,也便是第一个规则。但是我们可以想的更透彻一点,一个直接左递归的规则不论怎么样,最后一定是可以不断的生成他后面的式子的,例如A=>Aabc,他每使用这个规则,实际上就是整个式子从后面增加了一个abc。

那么这个?就是1

1是什么?1是一个规则,1是我们增加的第一个规则,我们后面修改规则还需要不断的增加,所以最好的方法就是用int值代表规则,每增加一个规则,int值递增,这样就不怕出现规则名字太长的问题。

而无数个a可以怎么表示呢?很简单,

1=>a1

空怎么表示呢?

1=>空

那么消除左递归后结果就是,

A=>a1

1=>a1

1=>空

这样直接左递归就消除了,那么间接左递归怎么办呢?什么是间接左递归

S=>B

B=>S

B=>a

我们使用规则1 得到 B ,然后用规则2,得到S,然后反复循环形成了间接左递归,而间接左递归的形成是为什么?很简单,因为环形,S=>B=>S  那么我们如何把环形切断呢? 我们可以让已经出现过的规则不允许再次出现在第一个字符。

S=>B  (S=true,B=>true)那么说明S和B都已经被访问过了,你不允许在第一个字符出现这个规则,因为就有可能形成环形。

那么看到第二个规则的时候我们发现了问题

B=>S (S=true,B=true) 哎?怎么规则出现过了啊?那现在怎么办呢?

我们将S换成他左边的式子

B=>B 接下来就变成了直接左递归,那么B=>a

最终获得的便是

S=>B

B=>a

这便将直接左递归消除了。

但是这个规则还是有可能有问题,例如什么?

A=>ab

A=>ac

此时进入一个字符a,请问你应该选择第一个规则还是第二个规则?

所以我们还需要继续改进这些规则。怎么做?我们将其合并,第一个字符既然都是a,那么我们就把他的第一个字符进行合并,然后后面的字符换成新的规则。

A=>a1

1=>b

1=>c

这样就能准确找到规则了。以上的步骤需要重复多次。

然后就是first集合和follow集合了,上面已经说过了,first集合即一个规则中出现的第一个终结符,如果规则第一个是非终结符,那么还要查看他是否为空。

A=>BC

B=>空

B=>d

C=>a

上面的first集合便是 A{d,空},B{d,空},C{a}

那么为什么要把空加进来?因为可以求follow集。

现在我们就可以求follow集合了。

B后面跟着C,所以C的first集合就属于B的follow集合,如果C可以为空,那么我们将followC的集合添加到followB身上。而对于followC来说,因为C处于规则1最后一项,所以A后面的符号也是C后面的符号,所以FollowA也是FollowC。

最终follow集合便是

A{END} B{a} C{END}

有了first集和follow集就能建立表了。首先我们还需要la集合,look ahead集合。通常我们是只看前面一个字符就足够了。

怎么做?

我们首先看第一个规则,A=>BC

根据first集合我们得知B的first集有d和空,所以当遇到d的时候,我们此时若栈中有A,就能利用此规则,然后此时我们还发现B有空,所以我们继续看,发现了C,C的first集合有a,所以当栈中出现a的时候我们也能使用第一个规则。

现在我们继续看第二条规则,B=>空, 那这怎么看呢?我们将followB的集合添加到这里来,也就是当栈中有B,此时出现a,我们就可以用规则2.

以此类推

最终的结果便是

la1{d,a}

la2{a}

la3{d}

la4{a}

有了la,我们就能进行分析了。

(a,A,)

(a,BC,1)查表得知可以使用规则1

(a,C,12)查表得知使用规则2

(a,a,124)查表使用规则4

(,,124)解析完成。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值