一、实验目的
通过预测分析算法的设计与实现,加深对自上而下语法分析方法的理解,尤其是对自上而下分析条件的理解。
二、实验内容
输入文法及待分析的输入串,输出其预测分析过程及结果。
1. 参考数据结构
(1)/*定义产生式的语法集结构*/
typedef struct{
char formula[200];//产生式
}grammarElement;
grammarElement gramOldSet[200];//原始文法的产生式集
(2)/*变量定义*/
char terSymbol[200];//终结符号
char non_ter[200];//非终结符号
char allSymbol[400];//所有符号
char firstSET[100][100];//各产生式右部的FIRST集
char followSET[100][100];//各产生式左部的FOLLOW集
int M[200][200];//分析表
2. 判断文法的左递归性,将左递归文法转换成非左递归文法。(该步骤可以省略,直接输入非左递归文法)。
3.根据文法求FIRST集和FOLLOW集。
4.构造预测分析表。
5.构造总控程序。
6.对给定的输入串,给出分析过程及结果。
设计思路:使用'$'代替符号ε;对于形如“A->B”这样一个产生式的保存,我设计了如下的结构体表示一个语法产生式,下面逐一讲解。
typedef struct {
char left; // 产生式左边,非终结符
int rcount; // 右边式子数量
char right[200][200]; // 产生式右边
int firstcount;
int followcount;
char First[200]; // first集合
char Follow[200]; // follow集合
} grammar;
grammar gramSet[200]; // 产生式集(直接输入LL(1)文法)
gramSet代表产生式集合,每一条产生式处理以后存放在gramSet中。先对读入的每一条产生式进行处理,依旧是“->”左边的是非终结符,对应成员left。“->”右边的是产生式右边的内容,如果遇到了“|”则代表后面的符号是该非终结符的又一条产生式,它对应rcount的数量,right数组是一个二维数组,如果一个非终结符对应有多个产生式,那么right数组则对应这个非终结符的产生式右部;每个产生式的左边是非终结符,都对应自己的FIRST集和FOLLOW集,其中firstcount和followcount又对应了它们各自的数量。
程序的开始输入相应的文法通过grammar_process函数处理,将文法中的终结符和非终结符分别记录下来,便于求FIRST集、FOLLOW集、预测分析表等。对于这些集和的运算,总体思路都是一一扫描每一条产生式集合,主要考虑ε是否要加入到FOLLOW集中,这样计算出FIRST集、FOLLOW集以后预测分析表就很好求了。
首先建立一个char* table[200][200],table表其实是一个三维数组,数组根据产生式左边的非终结符和终结符对应一种预测分析情况。对于预测分析表,即再次扫描每一条产生式,分析产生式右部首符的FIRST集(如果是$则分析这条产生式左边的非终结符的FOLLOW集),然后将对应的table表赋值即可。
对于总控程序,由于已经求出了预测分析表table,最主要的处理就是建立一个分析栈,并将‘#’和文法的开始符号压入栈中,接着,通过比较输入串指针指向的符号和分析栈的栈顶指向的符号即可,如果栈顶符号是非终结符,则通过此时输入串指针指向的符号和栈顶符号确定用table表中的某一产生式,随后将原先的非终结符出栈并将其产生式右部逆序的压入分析栈中(要注意如果这个时候产生式是$则不需要把$压入栈中);如果栈顶符号是终结符号,则需要比较栈顶符号和此时输入串指针指向的符号是否相同,如果相同则出栈比较下一位,知道两边的符号都指向结束符号“#”,此时预测分析过程成功并结束,一旦有不匹配的情况都会出错并停下来。
源代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char left; // 产生式左边,非终结符
int rcount; // 右边式子数量
char right[200][200]; // 产生式右边
int firstcount;
int followcount;
char First[200]; // first集合
char Follow[200]; // follow集合
} grammar;
grammar gramSet[200]; // 产生式集(直接输入LL(1)文法)使用'$'代替符号ε
typedef struct {
char* base; // 栈底
char* top; // 栈顶
int stacksize;
} stack;
int gramcount = 0; // 产生式数量
int tersymcount = 0; // 终结符数量
int nontercount = 0; // 非终结符数量
char startsym; // 文法开始符号
char terSymbol[200]; // 终结符号
char nonter[200]; // 非终结符号
char forecast_ter[200]; // 预测分析表用到的终结符
char* table[200][200]; // 分析表
char test[500]; // 要匹配的字符串
void read(); // 读入数据
void initstack(stack* s); // 初始化栈
void push(stack* s, char x); // 入栈
char get_top_stack(stack* s); // 获得栈顶
void pop(stack* s); // 出栈
int istersym(char ch); // 是否是终结符
void grammar_process(char ch); // 对产生式处理
void first(char sym); // 求sym的first集
void addfirst(char sym, char ch); // first(ch)并入first(sym)
void follow(char sym); // 求sym的follow集
void addfollow(char sym, char ch); // follow(ch)并入follow(sym)
void addfollow_to_follow(char sym, char ch); // 将ch的follow加入sym的follow中
void addfirst_to_follow(char sym, char ch); // 将ch的first加入sym的follow中
int nonull_infirst(char ch); // 判断一个非终结符的first中是否$
int get_terminal_index(char sym); // 获取终结符在表中的索引
int get_nonter_index(char sym); // 获取非终结符在表中的索引
void create_forecast(); // 创建预测分析表
void show(); // 打印预测分析表
void main_control(); // 主控程序
int main() {
printf("请输入文法: 以'#'结束\n");
read();
startsym = gramSet[0].left; // 开始符号
printf("输入串(以'#'结束)为:\n");
scanf("%s", test);
for (int i = 0; i < gramcount; i++) {
first(gramSet[i].left);
}
for (int i = 0; i < gramcount; i++) {
follow(gramSet[i].left);
}
printf("\n");
printf("FIRST集如下:\n");
for (int i = 0; i < gramcount; i++) {
printf("FIRST(%c): ", gramSet[i].left);
for (int j = 0; j < gramSet[i].firstcount; j++)
printf("%c ", gramSet[i].First[j]);
printf("\n");
}
printf("\n");
printf("FOLLOW集如下:\n");
for (int i = 0; i < gramcount; i++) {
printf("FOLLOW(%c): ", gramSet[i].left);
for (int j = 0; j < gramSet[i].followcount; j++)
printf("%c ", gramSet[i].Follow[j]);
printf("\n");
}
printf("\n");
printf("预测分析表如下:\n");
create_forecast();
show();
printf("\n");
printf("预测分析步骤如下:\n");
main_control();
return 0;
}
void read() { // 读入数据
int flag = 1;
char str[100];
while (flag) {
scanf("%s", str);
if (str[0] == '#') {
flag = 0;
break;
}
gramSet[gramcount].left = str[0]; // 产生式的左边
grammar_process(str[0]); // 处理左边
for (int i = 3; i < strlen(str); i++) {
// 右边0-2位是‘E->’,从第三位开始
int j = 0;
char ter[100];
while (str[i] != '|' && str[i] != '\0') {
grammar_process(str[i]); // 处理右边
ter[j++] = str[i++];
}
ter[j] = '\0';
// 处理一个终结符则放在的对应的产生式
strcpy(gramSet[gramcount].right[gramSet[gramcount].rcount], ter);
gramSet[gramcount].rcount++;
}
gramcount++;
}
terSymbol[tersymcount++] = '#'; // 所有符号读入以后把'#'加进去进去
}
int istersym(char ch) { // 是否是终结符
if (ch >= 'A' && ch <= 'Z')
return 0; // 大写字母非终结符
return 1;
}
void grammar_process(char ch) { // 将非终结符和终结符分别放在对应的数组中
if (!istersym(ch)) {
int flag = 0;
for (int i = 0; i < nontercount; i++) {
if (ch == nonter[i]) {
flag = 1;
break; // 已在非终结符集中
}
}
if (flag == 0) {
nonter[nontercount++] = ch;
}
} else {
int flag = 0;
for (int i = 0; i < tersymcount; i++) {
if (ch == terSymbol[i]) {
flag = 1;
break; // 已在终结符集中
}
}
if (flag == 0) {
terSymbol[tersymcount++] = ch;
}
}
}
void first(char sym) { // 求FIRST集
int i; // 标识sym在产生式集合中的位置
for (i = 0; i < gramcount; i++) {
if (gramSet[i].left == sym)
break; // 找到了该非终结符的位置
}
// 处理该终结符对应的每条产生式右部
for (int j = 0; j < gramSet[i].rcount; j++) {
int last = 0; // 标记当前产生式右部的最后一个字符是否包含在 First 集合中
// 如果 last 为 1,则表示右部的最后一个字符可推导为空($)
for (int s = 0; s < strlen(gramSet[i].right[j]); s++) { // 扫描一条产生式右部
int status = 0; // 判断是否继续获取下一个字符
char ch = gramSet[i].right[j][s]; // 逐个获取产生式右部字符ch
if (istersym(ch)) {
// 终结符或$
int flag = 0;
for (int n = 0; n < gramSet[i].firstcount; n++) {
if (gramSet[i].First[n] == ch) { // 如果已经存过就break
flag = 1;
break;
}
}
if (flag == 0) {
gramSet[i].First[gramSet[i].firstcount++] = ch; // 否则这一条产生式的FIRST集就添加元素
}
break;
} else {
if (!gramSet[i].firstcount) // 还没有求过ch的first集
first(ch);
// 如果是非终结符
int p;
for (p = 0; p < gramcount; p++) {
if (gramSet[p].left == ch)
break; // 找到该非终结符在产生式集合的位置
}
// 将first(ch)加入到first(sym)中(除了$)
for (int n = 0; n < gramSet[p].firstcount; n++) {
addfirst(sym, gramSet[p].First[n]); // 将该非终结符的First集合加入到sym的First集合中
}
if (!nonull_infirst(ch)) { // 若包含$需要进入循环,继续获取下一个字符
status = 1;
if (s + 1 == strlen(gramSet[i].right[j]))
last = 1;
}
}
if (status == 0)
break;
}
// 如果右边符号串可推出是空,将$加入
if (last == 1) {
int flag = 0;
for (int n = 0; n < gramSet[i].firstcount; n++) {
if (gramSet[i].First[n] == '$') {
flag = 1; // 已在First集中
break;
}
}
if (flag == 0)
gramSet[i].First[gramSet[i].firstcount++] = '$'; // 把$ 加进FIRST集
}
}
}
int nonull_infirst(char ch) { // 判断一个非终结符的first中是否不含‘$’
int i;
for (i = 0; i < gramcount; i++) {
if (gramSet[i].left == ch)
break;
} // 确认传入的非终结符对应哪一产生式集合
for (int j = 0; j < gramSet[i].rcount; j++) { // 遍历该非终结符对应的所有产生式右部
if (gramSet[i].right[j][0] == '$')
return 0;
}
return 1;
}
void addfirst(char sym, char ch) { // 将ch并入First(sym)
int i;
for (i = 0; i < gramcount; i++) {
if (gramSet[i].left == sym)
break; // 找到了该非终结符的位置
}
for (int j = 0; j < gramSet[i].firstcount; j++) {
if (gramSet[i].First[j] == ch)
return; // 已存在于first集中
}
gramSet[i].First[gramSet[i].firstcount++] = ch;
}
void follow(char sym) { // 求FOLLOW集
int p;
for (p = 0; p < gramcount; p++) {
if (gramSet[p].left == sym)
break;
}
// 是开始符号,需要加入'#'
if (sym == startsym) {
gramSet[p].Follow[gramSet[p].followcount++] = '#';
}
// 对全部的产生式找一个右部含有当前字符x的产生式
for (int i = 0; i < gramcount; i++) {
for (int j = 0; j < gramSet[i].rcount; j++) {
for (int k = 0; k < strlen(gramSet[i].right[j]); k++) {
if (gramSet[i].right[j][k] == sym) {
// X在产生式右部的最后(形如产生式A->aX),把FOLLOW(A)中的元素加入FOLLOW(X)
if (k + 1 == strlen(gramSet[i].right[j])) {
char ch = gramSet[i].left;
if (sym != ch) {
if (!gramSet[i].followcount) // 看是否操作过follow集
follow(ch);
addfollow_to_follow(sym, ch); // follow(ch)加入follow(sym)
}
}
// X不在产生式右部的最后(A->aXB )
else {
int last = 0; // 标记:判断B能否推出$
// 遍历B字符串中所有字符
for (int b = k + 1; b < strlen(gramSet[i].right[j]); b++) { // sym的后继字符ch
char ch = gramSet[i].right[j][b];
// 终结符直接加入follow(sym)
if (istersym(ch)) {
int flag = 0;
for (int n = 0; n < strlen(gramSet[p].Follow); n++) {
if (gramSet[p].Follow[n] == ch) { // 遍历syn为非终结符的FOLLOW集
flag = 1;
break;
}
}
if (flag == 0)
gramSet[p].Follow[gramSet[p].followcount++] = ch;
break;
}
// 非终结符
else {
addfirst_to_follow(sym, ch);
// ch的first中不含空值
if (nonull_infirst(ch)) {
break;
} else {
last++;
}
}
}
if (last + k + 1 == strlen(gramSet[i].right[j])) {
char ch = gramSet[i].left;
if (!gramSet[i].followcount)
follow(ch);
addfollow_to_follow(sym, ch);
}
}
}
}
}
}
}
void addfollow(char sym, char ch) { // 将字符ch加入sym的FOLLOW集中去
int i;
for (i = 0; i < gramcount; i++) {
if (gramSet[i].left == sym)
break; // 找到了该非终结符的位置
}
for (int j = 0; j < gramSet[i].followcount; j++) {
if (gramSet[i].Follow[j] == ch)
return; // 已存在于follow集中
}
gramSet[i].Follow[gramSet[i].followcount++] = ch;
}
void addfollow_to_follow(char sym, char ch) { // 将ch的folloe加入sym的follow中
int i, j;
for (i = 0; i < gramcount; i++) {
if (gramSet[i].left == sym)
break; // 找到了sym对应的位置i
}
for (j = 0; j < gramcount; j++) {
if (gramSet[j].left == ch)
break; // 找到了该ch对应的位置j
}
for (int k = 0; k < gramSet[j].followcount; k++) {
addfollow(sym, gramSet[j].Follow[k]);
}
}
void addfirst_to_follow(char sym, char ch) { // 将ch的first加入sym的follow中
int i, j;
for (i = 0; i < gramcount; i++) {
if (gramSet[i].left == sym)
break; // 找到了sym对应的位置i
}
for (j = 0; j < gramcount; j++) {
if (gramSet[j].left == ch)
break; // 找到了该ch对应的位置j
}
for (int k = 0; k < gramSet[j].firstcount; k++) {
// 遍历ch的FIRST集合,把不是'$'s的字符加入到sym的FOLLOW集
if (gramSet[j].First[k] == '$') {
for (int l = 0; l < gramSet[i].followcount; l++) {
addfollow(sym, gramSet[i].Follow[l]);
}
} else {
addfollow(sym, gramSet[j].First[k]);
}
}
}
int get_terminal_index(char sym) { // 获取终结符在表中的索引
for (int i = 0; i < tersymcount; i++) {
if (terSymbol[i] == sym) {
return i;
}
}
return -1; // 如果找不到,返回-1表示错误
}
int get_nonter_index(char sym) { // 获取非终结符在表中的索引
for (int i = 0; i < nontercount; i++) {
if (nonter[i] == sym) {
return i;
}
}
return -1; // 如果找不到,返回-1表示错误
}
void create_forecast() { // 创建预测分析表
for (int i = 0; i < gramcount; i++) { // 遍历每个产生式
char nonter = gramSet[i].left; // 保存这个非终结符
int nonter_index = get_nonter_index(nonter); // 获取非终结符在表中的索引
for (int j = 0; j < gramSet[i].rcount; j++) { // 遍历产生式右部
char x = gramSet[i].right[j][0];
// 如果产生式右部第一个符号是终结符
if (istersym(x)) {
// 如果是'$',根据nonter的FOLLOW集,对table表赋值
if (x == '$') {
for (int l = 0; l < gramSet[i].followcount; l++) {
char follow_sym = gramSet[i].Follow[l]; // FOLLOW集中的元素
int col_index = get_terminal_index(follow_sym); // 获取终结符在表中的索引
char temp[200];
sprintf(temp, "%c->%s", nonter, gramSet[i].right[j]);
table[nonter_index][col_index] = strdup(temp); // 使用strdup复制字符串
}
}
// 如果是其他终结符,直接根据终结符在表中的索引赋值
else {
int col_index = get_terminal_index(x);
char temp[200];
sprintf(temp, "%c->%s", nonter, gramSet[i].right[j]);
table[nonter_index][col_index] = strdup(temp); // 使用strdup复制字符串
}
}
// 如果产生式右部第一个符号是非终结符
else {
// 遍历该非终结符的FIRST集
for (int k = 0; k < gramSet[i].firstcount; k++) {
// FIRST集的元素
char first_sym = gramSet[i].First[k];
if (first_sym == '$') { // 如果FIRST集包含'$',根据nonter的FOLLOW集,对table表赋值
for (int l = 0; l < gramSet[i].followcount; l++) {
char follow_sym = gramSet[i].Follow[l];
int col_index = get_terminal_index(follow_sym); // 获取终结符在表中的索引
char temp[200];
sprintf(temp, "%c->%s", nonter, gramSet[i].right[j]);
table[nonter_index][col_index] = strdup(temp); // 使用strdup复制字符串
}
} else {
int col_index = get_terminal_index(first_sym);
char temp[200];
sprintf(temp, "%c->%s", nonter, gramSet[i].right[j]);
table[nonter_index][col_index] = strdup(temp); // 使用strdup复制字符串
}
}
}
}
}
}
void show() { // 显示预测分析表
printf("\t");
for (int i = 0; i < tersymcount; i++) {
if (terSymbol[i] != '$') { // 不输出‘$’
printf("%c\t", terSymbol[i]); // 打印表行
}
}
for (int i = 0; i < nontercount; i++) {
printf("\n%c\t", nonter[i]);
for (int j = 0; j < tersymcount; j++) {
if (terSymbol[j] == '$')
continue; // 如果遇到'$'跳过就行
else {
if (table[i][j] == NULL)
printf("error\t");
else
printf("%s\t", table[i][j]);
}
}
}
}
void initstack(stack* s) { // 栈的初始化函数
s->base = (char*)malloc(10 * sizeof(char));
if (!s->base) // 检测内存是否分配成功
exit(0);
s->top = s->base; // 将栈顶初始化为栈底
s->stacksize = 10;
}
void push(stack* s, char x) { // 入栈
if (s->top - s->base >= s->stacksize) {
s->base = (char*)realloc(s->base, (s->stacksize + 10) * sizeof(char));
if (!s->base)
exit(0);
s->top = s->base + s->stacksize; // 更新栈顶位置
s->stacksize += 10;
} // 检测是否发生上溢,再分配一些内存空间
*(s->top++) = x;
}
char get_top_stack(stack* s) {
return *(s->top - 1); // 返回栈顶元素
}
void pop(stack* s) { // 出栈
if (s->top != s->base) {
s->top--;
}
}
char* get_stack(stack* s) { // 创建一个足够大的字符数组来存储栈中的字符
char* result = (char*)malloc((s->stacksize + 1) * sizeof(char));
if (result == NULL) { // 处理内存分配失败的情况
return NULL;
}
char* result_ptr = result; // 指向result
char* temp_top = s->top; // 保存当前栈顶指针
s->top--; // 先让top指向下面的一个位置
char* temp_base = s->base; // 保存当前栈底指针
char tempArray[s->stacksize]; // 出栈并将元素保存在临时数组中
int i = 0;
while (s->top >= s->base) {
tempArray[i++] = *s->top--;
}
for (int j = i - 1; j >= 0; j--) { // 逆序输出临时数组中的元素到结果数组中
*result_ptr++ = tempArray[j];
}
*result_ptr = '\0'; // 在字符串的末尾添加终止符
s->top = temp_top; // 将指针回到栈顶
s->base = temp_base;
return result;
}
void main_control() { // 主控程序
int step = 1, i = 0; // i控制test位置
int flag = 1; // 循环控制标志
char* content;
stack* s = (stack*)malloc(sizeof(stack));
initstack(s);
push(s, '#');
push(s, nonter[0]); // 将'#'和文法开始符号压入栈
printf("步骤\t符号栈\t\t\t输入串\t\t\t所用产生式\n");
while (flag) {
char ch = test[i]; // 当前输入串符号指向的字符
int ch_index = get_terminal_index(ch); // 输入串当前字符在终结符表的位置
char top = get_top_stack(s); // 当前栈顶字符
int top_index = get_nonter_index(top); // 栈顶字符在非终结符表的位置
content = get_stack(s); // 当前栈内所有元素
char* test2 = (char*)malloc((strlen(test) - i + 1) * sizeof(char)); // 输入串的更新版,对应于当前指针指向哪里
strncpy(test2, test + i, strlen(test) - i + 1);
printf("%d\t%s\t\t\t%s", step++, content, test2);
free(test2);
if (!istersym(top)) {
pop(s); // 先让现在的非终结符出栈,然后让预测表的对应内容逆序入栈
for (int j = strlen(table[top_index][ch_index]) - 1; j > 2; j--) {
if (table[top_index][ch_index][j] != '$') { // 如果字符是$,则不处理
push(s, table[top_index][ch_index][j]);
}
}
printf("\t\t\t%s\n", table[top_index][ch_index]);
continue; // 操作一次就continue一次
} else {
if (top == ch) { // 栈顶是非终结符,判断与输入串当前位置是否匹配
printf("\n");
if (top == '#') {
printf("预测过程顺利!");
break; // 已经到结束的地方了
}
pop(s);
i++; // 输入串指针后移
} else {
printf("\n预测过程中出现匹配失败!");
break;
}
}
}
free(s->base); // 释放动态分配的内存
free(s);
}
实验结果记录
请输入文法: 以'#'结束
E->TP
P->+TP|$
T->FQ
Q->*FQ|$
F->(E)|i
#
输入串(以'#'结束)为:
(i+i)*i+i#
FIRST集如下:
FIRST(E): ( i
FIRST(P): + $
FIRST(T): ( i
FIRST(Q): * $
FIRST(F): ( i
FOLLOW集如下:
FOLLOW(E): # )
FOLLOW(P): # )
FOLLOW(T): + # )
FOLLOW(Q): + # )
FOLLOW(F): * + # )
预测分析表如下:
+ | * | ( | ) | i | # | |
E | error | error | E->TP | error | E->TP | error |
T | error | error | T->FQ | error | T->FQ | error |
P | P->+TP | error | error | P->$ | error | P->$ |
F | error | error | F->(E) | error | F->i | error |
Q | Q->$ | Q->*FQ | error | Q->$ | error | Q->$ |
预测分析步骤如下:
步骤 | 符号栈 | 输入串 | 所用产生式 |
1 | #E | (i+i)*i+i# | E->TP |
2 | #PT | (i+i)*i+i# | T->FQ |
3 | #PQF | (i+i)*i+i# | F->(E) |
4 | #PQ)E( | (i+i)*i+i# | |
5 | #PQ)E | i+i)*i+i# | E->TP |
6 | #PQ)PT | i+i)*i+i# | T->FQ |
7 | #PQ)PQF | i+i)*i+i# | F->i |
8 | #PQ)PQi | i+i)*i+i# | |
9 | #PQ)PQ | +i)*i+i# | Q->$ |
10 | #PQ)P | +i)*i+i# | P->+TP |
11 | #PQ)PT+ | +i)*i+i# | |
12 | #PQ)PT | i)*i+i# | T->FQ |
13 | #PQ)PQF | i)*i+i# | F->i |
14 | #PQ)PQi | i)*i+i# | |
15 | #PQ)PQ | )*i+i# | Q->$ |
16 | #PQ)P | )*i+i# | P->$ |
17 | #PQ) | )*i+i# | |
18 | #PQ | *i+i# | Q->*FQ |
19 | #PQF* | *i+i# | |
20 | #PQF | i+i# | F->i |
21 | #PQi | i+i# | |
22 | #PQ | +i# | Q->$ |
23 | #P | +i# | P->+TP |
24 | #PT+ | +i# | |
25 | #PT | i# | T->FQ |
26 | #PQF | i# | F->i |
27 | #PQi | i# | |
28 | #PQ | # | Q->$ |
29 | #P | # | P->$ |
30 | # | # |
预测过程顺利!
实验时发现的问题及解决过程
关于table表我一开始想的就是创建一个二维数组来存放,忽略了二维数组的话只能对应了非终结符和终结符以后存放单个字符,所以后面选择了char* table[200][200]这样的写法,让table表可以存放对应的产生式。对于FIRST集的求解,一开始没想到用递归的写法,因为在分析的过程中,一个非终结符的FIRST集可能与其他还未求解过的非终结符的FIRST集相关,如果未求解过那其中还没有内容,如果不在这时候递归的调用来求解这个还未算过的非终结符的FIRST集,就会一直跳过,最后缺少相应的元素,而FIRST集的求解也影响到了其他FIRST集或FOLLOW集的求解。其他问题相比起来比较好处理,最后我也学到了一个关于strncpy()函数的小技巧:在输出预测分析过程时,随着步骤的进展,输入串和栈的内容要动态的显示,其中获得栈的内容并逆序输出到一个字符串中比较容易,对于输入串的输出,因为输入串要求输出从当前指针指向的位置到字符串结尾的内容,选择用strncpy(test2, test + i, strlen(test) - i + 1)的方法输出,其中test2是目标串,最终想要输出的输入串内容用它表示,test是原本的输入串内容,i表示的是指针扫描到输入串的位置,这段话表示,从输入串的指针扫描的位置到一共复制剩下字符长度个内容,也即表示从指针指向的位置到最后的位置的内容,这样输入串的内容就能随着指针的扫描动态的展示了。通过本次实验我对FIRST集和FOLLOW集的求解印象也更深了。