基本概念见上篇
《lemon源码基本概念整理》
1. follow集
对于如下4条产生式
program ::= expr TK_SEM
expr ::= expr TK_IMPL expr
expr ::= TK_LPAREN expr TK_RPAREN
expr ::= TK_NEG expr
现在要求expr ::= TK_LPAREN expr TK_RPAREN*的follow集方法如下
expr ::= TK_LPAREN expr TK_RPAREN*
= expr ::= TK_LPAREN expr* TK_RPAREN
= expr ::= TK_LPAREN *expr TK_RPAREN
= expr ::= *TK_LPAREN expr TK_RPAREN
上面这4条,后面是前面的bplp,这个见上篇文章的最后。同时前面也就是后面的fplp,代码如下
expr ::= *TK_LPAREN expr TK_RPAREN的follow集由下面这些项目决定
program ::= *expr TK_SEM
expr ::= * expr TK_IMPL expr
expr ::= expr TK_IMPL *expr
expr ::= TK_LPAREN *expr TK_RPAREN
expr ::= TK_NEG *expr
代码在Configlist_closure函数里
反正经过上面的步骤最终可以求得expr ::= TK_LPAREN expr TK_RPAREN的follow集是下面这3个
这个也符号我们对follow集的直观感受,那么follow集在哪里会用到呢?这个在规约时会用到,见下面代码
也就是在产生式最右边时,会把这个项目的follow集加到归约动作里
2. 压缩动作链表
这个就是把本来这样的状态输出
State 9:
expr ::= expr * TK_IMPL expr
(1) expr ::= expr TK_IMPL expr *
TK_IMPL reduce 1
TK_SEM reduce 1
TK_RPAREN reduce 1
压缩成
State 12:
expr ::= expr * TK_IMPL expr
(1) expr ::= expr TK_IMPL expr *
{default} reduce 1
代码如下,在CompressTables函数里
这里之所以if( rp2==rbest ) continue;的原因是,绿框这个循环在rbest赋值之前就已经把rp的数量统计好了,当rbest赋值后已经是下一个大循环了,所以不必再对rbest做处理
3.类型联合
所有终结符和非终结符类型组成一个联合体,定义成一个YYMINORTYPE类型,代码在print_stack_union里,
4.动作数组
4.1 框架
首先定义一个ax数组,这个数组有2*nstate个元素,每2个元素分为终结符和非终结符关联绑定一个stp,代码如下
整体流程代码如下
首先肯定是遍历所有ax数组,获得状态stp,然后分为终结符和非终结符两个分支进行处理。这里值得注意的是,对于每个stp,先遍历所有的动作链表ap,同时执行acttab_action把动作数据放在pActtab->aLookahead里。执行完for循环后调用acttab_insert把该状态里的所有动作数据放在p->aAction里,并把pActtab->aLookahead清零
4.2 动作的数值
原先动作由struct action结构体表示,生成语法解析文件后会转换成一个整数。对于移进动作,动作取对应的状态编号。对于归约动作,动作数值取状态总数加对应的产生式编号,相关代码如下
4.3 每个状态的所有动作
每个状态的所有动作会放在pActtab->aLookahead里,每增加一个动作nLookahead加1,同时会记录该状态中动作关联的先行符的最小值和最大值,代码实现在acttab_action函数里
可以看到每个动作和先行符绑定在一起放在aLookahead里,aLookahead的每个元素是紧挨在一起的
4.4 所有状态的动作插入
每遍历完一个状态的所有动作后,会把aLookahead的动作数据转移到aAction数组里,因为aAction的数据会导入到yy_action里,只要传入当前状态和先行符就可以通过yy_action求得当前的动作,公式是
yy_action[ yy_shift_ofst[S] + X ]
代码实现在acttab_insert里,aAction数组的下标是先行符,一般来说数组里会有很多空闲,后一个状态的数据插入时会先查看能不能往空隙里插数据,如果能的话就像两把梳子对接起来,不行的话只能往数组的最后添加了
最后返回i - p->mnLookahead,这个值存放在stp->iTknOfst里,将来会导入yy_shift_ofst[S]数组,即每个状态的最小先行符在aAction数组里的起始位置
再来分析一下前面蓝框里的代码,这个代码有点莫名奇妙的
for(j=0; j<p->nLookahead; j++){
k = p->aLookahead[j].lookahead - p->mnLookahead + i;
if( k<0 ) break;
if( p->aAction[k].lookahead>=0 ) break;
}
if( j<p->nLookahead ) continue;
for(j=0; j<p->nAction; j++){
if( p->aAction[j].lookahead==j+p->mnLookahead-i ) break;
}
if( j==p->nAction ){
break;
}
因为前面的for循环已经出来了,到这里说明已经找到空隙。 p->aAction[j].lookahead== j+p->mnLookahead-i这里的j绝对不可能是上面的k,因为
p->aAction[k].lookahead<0
k+p->mnLookahead-i>0
所以不可能满足p->aAction[j].lookahead==j+p->mnLookahead-i这个条件,那么这个j就不是新加入的动作,而是原来状态的动作,为什么原来的状态和现在的状态的最小先行符mnLookahead不能相同呢,这个还不知道原因
5.语法分析
lemon文件运行后会输出一个语法分析的.c文件,再来看看这个.c文件是怎么工作的
首先会根据先行符和当前的状态,找到对应的动作,代码如下
获得动作的值后,就可以判断是移进还是规约,进而到相应的函数里处理
yy_shift时会更新当前的状态
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)