构造LR(1)项集族
在龙书的P167中为我们提供了为某一个文法G’构造LR(1)项集族的完整算法,照搬如下:
SetOfItems CLOSURE(I) {
repeat
for(I中的每个项[A->α·Bβ,a])
for(G'中的每个产生式B->γ)
for(FIRST(βa)每个终结符号b)
将[B->·γ,b]加入到集合I中;
until 不能向I中加入更多的项;
return I;
}
SetOfItems GOTO(I,X) {
将J初始化为空集;
for(I中的每个项[A->α·Xβ,a])
将项[A->αX·β,a]加入到集合J中;
return CLOSURE(J);
}
void items(G') {
将C初始化为{CLOSURE}({[S'->·S,$]});
repeat
for(C中的每个项集I)
for(每个文法符号X)
if(GOTO(I,X)非空且不在C中)
将GOTO(I,X)加入C中;
until 不再有新的项集加入到C中;
}
上述的代码只是从理论角度提供的可行的算法,而具体怎么写还要根据我们的实现来进行修改。上边的item
便是计算LR(1)项集族的主要函数,它会分别调用CLOSURE
函数计算一个内核项所产生的项集,之后调用GOTO
函数计算一个项集在遇到一个终结符或者非终结符时所产生的内核项。(内核项的概念十分重要,内核项是指包括初始项S'->·S
以及点不在最左端的所有项,一个项集其实只用内核项表示就足够了,两个项集如果内核项一样,那这两个项集一定是相同的。)
计算FIRST(βa)
注意到函数CLOSURE
在使用时调用了一个并未在算法中出现的函数FIRST
,函数FIRST(α)
被定义为可从α推导得到的串的首符号的集合,其中α是任意的文法符号串。虽然龙书中没有给出伪代码的算法,但是计算的规则已经表达得很清楚了:
1. 如果X是一个终结符号,那么FIRST(X)=X
。
如果X是一个非终结符号,且X->Y1Y2···Yk是一个产生式,其中k≥1,那么如果对于某个i,a在FIRST(Yi)中且ε在所有的FIRST(Y1)、FIRST(Y2)、···、FIRST(Yi-1)中,就把a加入到FIRST(X)=X中。
如果X->ε是一个产生式,那么将ε加入到FIRST(X)中。
那么我们现在就按照上述的方式给出一个
FIRST(α)
的具体实现:
typedef struct _used
{
int use[100];
int num;
}used;
void count_first(element *e, element **first_elements, used *is_used)
{
item *temp;
if (!e)
return;
for (int i = 0; i < is_used->num; i++)
{
if (e->is_terminator&&e->type.t_index == is_used->use[i])
return;
else if (!e->is_terminator&&e->type.pro->p_index == is_used->use[i])
return;
}
if (e->is_terminator)
{
is_used->use[is_used->num++] = e->type.t_index;
add_e2e(e, first_elements);
} else
{
is_used->use[is_used->num++] = e->type.pro->p_index;
temp = e->type.pro->items;
for (; temp; temp = temp->next)
{
if (temp->ele->is_terminator&&a