一. 实验目的及实验环境
(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);
}