Chinaunix首页 | 论坛 | 博客
  • 博客访问: 264608
  • 博文数量: 52
  • 博客积分: 1379
  • 博客等级: 大尉
  • 技术积分: 525
  • 用 户 组: 普通用户
  • 注册时间: 2006-06-18 17:34
文章分类

全部博文(52)

文章存档

2011年(48)

2010年(4)

分类:

2010-10-01 15:20:02

lr0函数代码

void
lr0(void)
{
    set_derives();
    set_nullable();
    generate_states();
}


lr0函数生成lr0状态机

set_derives


该函数计算各个非终结符对应哪些规则,在calc.y中如下:




set_nullable();


该函数计算文法符号是否可空
初始所有文法符号都不可空,通过ritem(如下)

就能得到所有规则的右边符号(如果有的话) ,如果右边是空,那么标记其左边符号可空
如果遍历了一遍ritem发现某个符号可空,那么还需要继续,因为可能有这种规则
A: B;
其中B在上一趟计算出来可空,而A由于计算的时候B还是非空而没有计算出来,这次可以计算出来A可空,因为A右端所有的符号都可空

不断继续,可以看出会结束
结束之后,所有可空的也都计算完了

在该例子只有list可空



done_flag = 1;
    for (i = 1; i < nitems; i++)
    {
     empty = 1;
     while ((j = ritem[i]) >= 0)
     {
        if (!nullable[j])
         empty = 0;
        ++i;
     }
     /* 右边是空,或者右边项目都可空 */
     if (empty)
     {
         /* 项目i开始的项目对应的规则的左边 */
        j = rlhs[-j];
        if (!nullable[j])
        {
            /* 以前认为规则j不能是空的,现在可以空
             * j可能存在于某个j2的右边, 所以需要重新遍历
             * 但是很明显,总能结束
             */

         nullable[j] = 1;
         done_flag = 0;
        }
     }
    }


generate_states();


分配项目空间

调用 allocate_itemsets 函数分配每个状态对应的项目集合需要用到的数据结构:

1. kernel_base 和 kernel_items



kernel_base为每个文法符号准备一个指针,它的具体位置在kernel_items中
假设文法符号X在规则右边出现了n次,那么它对应n个位置,比如$end只有一次($accept: goal $end)
所以$end只向0号位置,并且占用一个位置,DIGIT的次数为2, 如下是calc.y规则

number:  DIGIT
         {  $$ = $1; base = ($1==0) ? 8 : 10; }
      |  number DIGIT
         {  $$ = base * $1 + $2; }
      ;
显然$accept没有出现在右边, UMINUS也没有(它只是记录优先级),所以不占位置

这种指针+数组的结构比较紧凑,byacc好多数据结构都是这种存储方式,以前也遇到过。

每个文法符号出现多少次是通过遍历数组ritem得到的,比较简单。

2. shiftset, redset, state_set

shiftset用于在某个状态上记录可以转移到哪些状态
redset用于在某个状态上记录可以有哪些规则可规约
state_set用于查找某个状态集合是否已经对应了某个状态(号)

set_first_derives

该函数首先调用set_EFF对每个非终结符A计算它相应规则右端第一个可能的非终结符是什么
以及第一个右端非终结符对应的规则第一个非终结符, ...一直传递下去
并且把该A本身也带上

比如 A: B X |D X
B: C X
D: E

那么A将计算出 BCED等符号



记录在名字为EFF的位图数组中,每个非终结符对应一个位图
位图可以涵盖所有的非终结符, 如下所示


/* nvars * nvars bits */
rowsize = WORDSIZE(nvars);
EFF = NEW2(nvars * rowsize, unsigned);



nvars就是非终结符的个数
计算EFF的方法:

首先每个终结符对应的规则在derives已经有记录
而每个规则右边第一个符号记录在rrhs中(reader)
每个符号是不是终结符跟start_symbol比较就可以了, start_symbol ~ nsyms都是非终结符
自反传递闭包由warshall算法计算
在reflexive_transitive_closure函数中

对于calc.y,如下:


直观上看,当我们期待一个expr的时候,也允许number出现

在任何非终结符A 得到 A:B X中的所有B之后,就可以计算相应的B出现在哪些规则中
把这些规则号保存在一个位图数组中,和EFF类似,只是这时位图代表规则号,而EFF代表非终结符

每个B来自哪些规则由derives给出, 所以计算比较简单,一个个终结符遍历EFF对应的位图和derives就可以了


对于calc.c:



First Derives

$accept derives
   2
   3
   4
   5

list derives
   3
   4
   5

stat derives
   6
   7
   8
   9
   10
   11
   12
   13
   14
   15
   16
   17

expr derives
   8
   9
   10
   11
   12
   13
   14
   15
   16
   17

number derives
   16
   17



把它们的值减去2之后就和如下内容可以对应


0 $accept : list $end

   1 list :
   2 | list stat '\n'
   3 | list error '\n'

   4 stat : expr
   5 | LETTER '=' expr

   6 expr : '(' expr ')'
   7 | expr '+' expr
   8 | expr '-' expr
   9 | expr '*' expr
  10 | expr '/' expr
  11 | '-' expr
  12 | LETTER
  13 | number

  14 number : DIGIT
  15 | number DIGIT


可以看出$accept 对应前4条规则,其它类似

initialize_states


在一切准备就绪之后,generate_states调用该函数生成状态0,它只包含一个核心项目
在calc.y 中,它是

$accept: . list $end

状态的意义:

语法分析的目标就是识别goal,在calc.y中它是一个list,对C语言来说它是翻译单元,然后遇到EOF就算成功。
所以刚开始的时候我们期待看到$accept,这正是构造$accept的原因

项目代表我们期望看到什么,刚开始我们希望要得到$accept, 我们期待出现一个list(而且必须有一个list)
状态0仅仅包含一个核心项目的原因也就是$accpet只有一个规则的原因。
当然由于list可以推导出其它符号,所以该状态也会包含一些非核心项目,只是这些非核心项目并不保存在状态中



/* 生成状态0, 为其它状态做准备 */
static void
initialize_states(void)
{
    unsigned i;
    short *start_derives;
    core *p;

    /* rlhs[2] = start_symbol;
     * $accept 只有规则0,其值是2
     */

    start_derives = derives[start_symbol];
    for (i = 0; start_derives[i] >= 0; ++i)
    continue;

    assert(i == 1);

    p = (core *)MALLOC(sizeof(core) + i * sizeof(short));
    NO_SPACE(p);

    p->next = 0;
    p->link = 0;
    p->number = 0; /* 状态号 */
    p->accessing_symbol = 0; /* 什么符号到达该状态 */
    p->nitems = (Value_t) i;

    for (i = 0; start_derives[i] >= 0; ++i)
    p->items[i] = rrhs[start_derives[i]];

    /* 状态0只有一个项目, 即 $accept: . goal $end */
    assert(i == 1 && ritem[p->items[0]] == start_symbol+1 /* goal */);

    first_state = last_state = this_state = p;
    nstates = 1;
}



生成其它状态


从语句     p->items[i] = rrhs[start_derives[i]]; 可以看出项目的表示就是ritem的位置

在initialize_states生成状态0后,generate_states不断的循环生成其它的状态,直到不会有更多的状态




while (this_state)
    {
    closure(this_state->items, this_state->nitems);
    save_reductions();
    new_itemsets();
    append_states();

    if (nshifts > 0)
     save_shifts();

    this_state = this_state->next;
    }


1. 求非核心状态

closure根据核心项目生成非核心项目
比如对于状态0 $accept: list $end

对任何 list推导Aabc的A在first_derives中保存着,把这些A对应规则A:X Y对应的项目A:. X Y
加入到当前状态(现在0)中,就得到所有的项目,这些项目就表示,我们接下来该看到什么:
就是这些项目中的某个符号

这些项目保存在itemset中, 这只是临时为生成状态用的,大小是nitems, itemsetend是当前状态
所有项目的结尾

2. 当前状态是否可以规约

因为itemset中保存了所有的项目,save_reductions可以知道某个项目是否是规则的结尾处:


count = 0;
    for (isp = itemset; isp < itemsetend; isp++)
    {
    item = ritem[*isp];
    if (item < 0)
    {
     redset[count++] = (Value_t) - item;
    }
    }


参考ritem,每个规则结尾存有规则号的相反数

redset就表示有哪些规则可以规约

reductions结构用来保存一个状态的规约,它记录了相应的状态号(依次递增每生成一个状态有一个号)以及有几个规约等信息

3. 移进符号产生新状态

itemset中所有不是结尾的项目 A: X . Y,移进Y后可以得到新的状态,所有的符号Y保存在shift_symbol中

这是由new_itemsets来完成的


for (i = 0; i < nsyms; i++)
    kernel_end[i] = 0;

    shiftcount = 0;
    isp = itemset;
    while (isp < itemsetend)
    {
    i = *isp++;
    symbol = ritem[i];
    if (symbol > 0)
    {
     ksp = kernel_end[symbol];
     if (!ksp)
     {
        shift_symbol[shiftcount++] = symbol;
        ksp = kernel_base[symbol];
     }

     *ksp++ = (Value_t) (i + 1);// A: X . Y Z  TO A: X Y .Z
     kernel_end[symbol] = ksp;
    }
    }


结果保存在kernel_base指向的位置中!! kernel_base 为每个符号保留了足够的位置


接下来的append_states比较简单了


for (i = 0; i < nshifts; i++)
    {
    symbol = shift_symbol[i];
    shiftset[i] = get_state(symbol);
    }


对每个shift_symbol中的symbol, 移近之后有哪些项目在kernel_base[symbol]和kernel[end]中保存着,即看到symbol之后还能看到什么,当然这些项目是核心项目

get_state比较两个状态是否等价,如果是就范围状态号,否则新建状态

和reduction类似 shifts结构保存移进信息, 由save_shifts完成

至此,由于状态数是有限的(所有项目元素集合的幂集个数),generate_states函数的不断生成新状态的循环总会结束

所有的状态生成,规约信息和移进都有了
阅读(2877) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~