一. 實驗目的及實驗環境
(1)實驗目的:熟悉語法分析的過程,編寫代碼實現判斷LL(1)文法
並判斷一個句子是否屬於該文法。
(2)實驗環境:ubuntu14.04,使用的工具vim, gcc, gdb
二. 實驗內容
(1)輸入任意文法,消除左遞歸和公共左因子;
(2)打印文法的First和Follow集;
(3)判斷是否是LL(1)文法,如果是則打印其分析表;
(4)輸入一個句子,如果該句子合法則輸出與句子對應的語法樹;
能夠輸出分析過程中每一步符號棧的變化情況。
如果該句子非法則進行相應的報錯處理。
三.方案設計
重要的數據結構
產生式的數據結構
/* 表示一條產生式 */
struct productive {
struct node *head;
/* 表示產生式的長度,如上個產生式的長度是4 */
unsigned length;
};
文法的數據結構
/* 文法 */
struct sentence {
struct productive *prod;
/* 產生式的實際長度 */
unsigned cnt;
/* 產生式表的長度 */
unsigned size;
}
First 和 follow 集的數據結構
/* ==================================================================
* 下面是非終結符的First follow 集合的數據結構
* ================================================================== */
struct ff_node {
/* 非終結符少的多,所以右移兩位 */
char ne_sign[NODE_MAX>>2];
char first[NODE_MAX];
char follow[NODE_MAX];
/* 分別用最低位,次低位表示first, follow 集合有沒有求完(1完) */
unsigned char f;
};
struct nes_table {
struct ff_node *table;
size_t cnt;
size_t size;
};
/* ================================================================== */
分析表的數據結構
/* ==================================================================
* 下面是分析表的結構
* ================================================================== */
/* 分析表中的一個節點類型 */
#define SYNCH 1
struct anat_node {
struct node *row_add; /* 產生式表中的第幾條 */
struct node *col_add; /* 對應產生式中那的第幾個 */
unsigned char err; //
};
/* 分析表結構體 */
struct ana_table {
struct anat_node *table;
struct e_ne_signs *signs;
};
<p>/* ================================================================== */ </p><p><pre name="code" class="html"><span style="font-family: 'Times New Roman'; font-size: 12pt; background-color: rgb(255, 255, 255);">整個語法分析的函數調用關係圖</span>
(1)消除左遞歸
循環判斷每條產生式(每條產生式的右部一一判斷)是否含有左遞歸,
若含有調用下面代碼段消除左遞歸。
主要代碼段:清除一條產生式裏的左遞歸
void clean_direct_a_prod (struct sentence *s, size_t i)
{
int len, flag = 0;
struct node *head, *p, *q, *node;
struct productive new;
head = s->prod[i].head;
len = strlen (head->val);
q = head->next;
/* 判斷是否含有直接左遞歸 */
if (0 == strncmp (head->val, q->val, len)) {
/* 添加的產生式的頭部 */
memset (&new, 0, sizeof (struct productive));
MALLOC_AND_fill (node, struct node, node->val, head->val, len);
strncat (node->val, "\'", 1);
new.head = node;
node->next = head->next;
s->prod[i].length = new.length = 1;
for (q = head->next; q; q = q->next) {
if (0 == strncmp (head->val, q->val, len)) {
memmove (q->val, q->val+len, strlen (q->val)-len+1);
strncat (q->val, new.head->val, len+1);
new.length++;
p = q;
}else break;
}
MALLOC_AND_fill (node, struct node, node->val, EMPTY_SET, strlen (EMPTY_SET));
p->next = node; /* 給新的產生式添加空元素 */
new.length++;
head->next = q;
if (NULL == q) {
MALLOC_AND_fill (node, struct node, node->val, new.head->val, len+1);
head->next = node;
s->prod[i].length += 1;
}
for (q; q; q = q->next) {
strncat (q->val, new.head->val, len+1);
s->prod[i].length += 1;
}
/* 添加新的產生式到文法中 */
add_P_2_S (s, &new);
}
}
(2)構造First和Follow集
按照First集得規則:
A)若X屬於VT,則FIRST(X)= {X};
B)若X屬於VN,且右產生式X->a...,則把a加入到FIRST(X)中;
若X->ε也是一條產生式,則把ε也加入到FIRST(X)中;
C)若X->Y... 是一條產生式且Y屬於VN,則把FIRST(Y)中的所有非ε
元素都加到FIRST(X)中; 若X->Y1Y2...YK是一個產生式,Y1...Yi-1
都是非終結符,而且,對於任何j,1<=j<=i-1, FIRST(Yj)都含有ε,
則把FIRST(Yi)中的所有非ε元素都加到FIRST(X)中;
構造FIRST集的重要代碼段
void first (const struct sentence *s, struct nes_table *ff, size_t i)
{
if (get_first (ff->table[i]))
return ;
struct node *p = s->prod[i].head->next;
int index, k;
for (p; p; p = p->next) {
index = get_index (ff, p->val);
if (-1 == index) {
/* 不加如重複的終結符 */
for (k = 0; ff->table[i].first[k] && ff->table[i].first[k] != p->val[0]; k++) ;
if (0 == ff->table[i].first[k])
ff->table[i].first[k] = p->val[0];
}else {
first (s, ff, index);
strncpy (ff->table[i].first, ff->table[index].first, \
strlen (ff->table[index].first));
}
}
set_first_1 (ff->table[i]);
}
按照Follow集得規則:
A)對於文法的開始符號S,置#到FOLLOW(S)中;
B)若A->αΒβ是一個產生式,則把FIRST(β)去除ε加至FOLLOW(B)中;
C)若A->αΒ是一個產生式,或A->αΒβ是一個產生式而ε屬於FIRST(β),
則把FOLLOW(A)加至FOLLOW(B)中;
構造FOLLOW集的重要代碼段
/* 判斷的是一條產生式 */
void follow (const struct sentence *s, struct nes_table *ff, size_t i)
{
struct node *head = s->prod[i].head;
struct node *p = head->next;
int pos, index, cur, len;
for (p; p; p = p->next) {
for (pos = 0; p->val[pos]; ) {
/* 當前判斷的非終結符,B */
cur = index = get_index (ff, p->val+pos);
/* 是非終結符 */
if (-1 != index) {
/* 非終結符後的下一個字符的位置 */
pos += strlen (ff->table[index].ne_sign);
/* 如B的後邊沒有字符,則加入follow A 到 follow B */
if (0 == p->val[pos]) {
int i = get_index (ff, head->val);
strncat (ff->table[cur].follow, ff->table[i].follow, strlen (ff->table[i].follow));
}else {
/* 當前非終結符後的符號 */
index = get_index (ff, p->val + pos);
/* 是終結符 */
if (-1 == index) {
strncat (ff->table[cur].follow, p->val+pos, 1);
pos++;
}else {
/* 添加出去空元素的 first 集 */
char *buf = ff->table[index].first;
int pos = 0, k = strlen (ff->table[cur].first);
for (pos = 0; buf[pos]; pos++) {
if (EMPTY_ALP != ff->table[index].first[pos])
ff->table[cur].follow[k++] = ff->table[index].first[pos];
}
/* 添加 follow */
if (1 == is_get_null (s, ff, index)) {
int i = get_index (ff, head->val);
strncat (ff->table[cur].follow, ff->table[i].follow, strlen (ff->table[i].follow));
}
}
}
}else pos++;
}
}
}
(3)構造分析表
按照規則先判斷消除左遞歸後的文法是否爲LL(1)文法
A)文法不含左遞歸
B)對於文法的每個非終結符A的各個產生式的候選首符集兩兩不相交
C)對於文法的每個非終結符A,若它存在某個候選首符集包含ε,z則
FIRST(A)Π FOLLOW(A)= Φ
代碼實現如下
/* 判斷是否爲LL1文法, 是LL1文法返回1,否則返回0 */
int decide_LL1 (const struct sentence s, const struct nes_table ff)
{
int i;
struct node *p, *q;
/* 判斷每個產生式中的候選首集是否有交集 */
for (i = 0; i < s.cnt; i++) {
for (p = s.prod[i].head->next; p; p = p->next) {
/* 判斷包含空集的非終結符的first follow 集是否有交集 */
if ( !strncmp (p->val, EMPTY_SET, strlen (EMPTY_SET)) )
if ( is_strs_intersect (ff.table[i].first, ff.table[i].follow) )
return 0;
for (q = p->next; q; q = q->next) {
/* 判斷包含空集的非終結符的first follow 集是否有交集 */
if ( !strncmp (q->val, EMPTY_SET, strlen (EMPTY_SET)) )
if ( is_strs_intersect (ff.table[i].first, ff.table[i].follow) )
return 0;
int index1 = get_first (ff, p->val);
int index2 = get_first (ff, q->val);
if (index1 == index2)
return 0;
else {
char buf1[NODE_MAX] = {0}, *f1 = ff.table[index1].first;
char buf2[NODE_MAX] = {0}, *f2 = ff.table[index2].first;
0 > index1 ? snprintf (buf1, 1, "%c", (char)(-index1)) :
snprintf (buf1, strlen (f1), "%s", f1);
0 > index2 ? snprintf (buf2, 1, "%c", (char)(-index2)) :
snprintf (buf2, strlen (f2), "%s", f2);
/* 判斷候選首符集是否相交 */
if ( is_strs_intersect (buf1, buf2) )
return 0;
}
}
}
}
return 1;
}
按照分析表的規則構造分析表
重要代碼實現如下
void show_ana_table (const struct ana_table an)
{
int i, j;
char *buf;
printf ("分析表 \n");
print_line (10, an.signs->cols + 1);
buf = an.signs->e_sign;
printf ("| %-10s", "");
while ( *buf ) printf ("| %-10c", *buf++);
printf ("|\n");
print_line (10, an.signs->cols + 1);
for (i = 0; i < an.signs->rows; i++) {
printf ("| %-10s", an.signs->ne_sign[i].val);
for (j = 0; j < an.signs->cols; j++) {
char temp[NODE_MAX] = "";
char *buf1 = an.table[i * an.signs->cols + j].row_add->val;
char *buf2 = an.table[i * an.signs->cols + j].col_add->val;
if (buf1 && buf2)
sprintf (temp, "%s->%s", buf1, buf2);
else if (SYNCH == an.table[i * an.signs->cols + j].err)
sprintf (temp, "%s", "synch");
else
sprintf (temp, "%s", "");
printf ("| %-10s", temp);
}
printf ("|\n");
print_line (10, an.signs->cols + 1);
}
}
/* 添加一條記錄到分析表中 */
static void add_ana_table (const struct ana_table *an,int row,char e,const void *row_add,const void *col_add,struct nes_table ff)
{
/* 若e爲空字符,則吧e屬於follow(A)把產生式加入至M[A, e]中*/
if (EMPTY_ALP == e) {
char *buf = ff.table[row].follow;
while ( *buf )
add_ana_table (an, row, *buf++, row_add, col_add, ff);
return ;
}
/* 若e終結符,則把產生式加入至M[A, e]中*/
int col;
for (col = 0; col < an->signs->cols; col++) {
if (e == an->signs->e_sign[col])
break ;
}
/* 添加 sentence 表中的地址 */
an->table[row * an->signs->cols + col].row_add = (struct node*)row_add;
an->table[row * an->signs->cols + col].col_add = (struct node*)col_add;
}
(4)構造分析棧檢錯並打印語法樹
/* 分析表和輸入串,同時構造語法樹 */
void analyse_stack (const struct ana_table an, const char *str)
{
struct ana_stack ana_stack = {0};
/* 將#,和文法的開始符號加入棧低 */
push_stack (&ana_stack, "#");
push_stack (&ana_stack, an.signs->ne_sign[0].val);
/* 語法樹根 */
struct tree_node *tree = NULL, *tree_pos;
if (0 > add_node (&tree, 0, an.signs->ne_sign[0].val))
printf ("tree error ! \n");
tree_pos = tree;
/* 表示掃描串的位置 */
int pos = 0, len = strlen (str);
/* 棧爲空或字符串匹配完則結束 */
display (ana_stack, str, "");
while (0 < ana_stack.cnt && pos < len && strncmp (get_top (ana_stack), "#", 2)) {
const char *buf = analyse_str (&ana_stack, an, str[pos], &pos, &tree_pos);
display (ana_stack, str+pos, buf);
}
display_tree (tree);
destroy_tree (tree);
}