2023/12/06更新main_control()函数
之前的写法不借助归约时将N加入栈中,导致归约运算符时一直没有想到更好的办法合理归约和对错误的输入串报错,本次改动摒弃了之前的做法,一次成功的归约后将N加入栈中,已解决问题。
一、 实验目的
根据算符优先分析法,对表达式进行语法分析,使其能够判断一个表达式是否正确。通过算符优先分析方法的实现,加深对自下而上语法分析方法的理解。
二、 实验内容
1、输入文法。可以是如下算术表达式的文法(你可以根据需要适当改变):
E→E+T|E-T|T
T→T*F|T/F|F
F→(E)|id
2、对给定表达式进行分析,输出表达式正确与否的判断。
程序输入/输出示例:
输入:1+2
输出:正确
输入:(1+2)/3+4-(5+6/7)
输出:正确
输入:((1-2)/3+4
输出:错误
输入:1+2-3+(*4/5)
输出:错误
3、参考数据结构
char *VN=0,*VT=0;//非终结符和终结符数组
char firstvt[N][N],lastvt[N][N],table[N][N];
typedef struct //符号对(P,a)
{
char Vn;
char Vt;
} VN_VT;
typedef struct //栈
{
VN_VT *top;
VN_VT *bollow;
int size;
}stack;
4、根据文法求FIRSTVT集和LASTVT集
给定一个上下文无关文法,根据算法设计一个程序,求文法中每个非终结符的FirstVT 集和LastVT 集。
算符描述如下:
/*求 FirstVT 集的算法*/
PROCEDURE insert(P,a);
IF not F[P,a] then
begin
F[P,a] = true; //(P,a)进栈
end;
Procedure FirstVT;
Begin
for 对每个非终结符 P和终结符 a do
F[P,a] = false
for 对每个形如 P a…或 P→Qa…的产生式 do
Insert(P,a)
while stack 非空
begin
栈顶项出栈,记为(Q,a)
for 对每条形如 P→Q…的产生式 do
insert(P,a)
end;
end.
同理,可构造计算LASTVT的算法。
5、构造算符优先分析表
依据文法和求出的相应FirstVT和 LastVT 集生成算符优先分析表。
算法描述如下:
for 每个形如 P->X1X2…Xn的产生式 do
for i =1 to n-1 do
begin
if Xi和Xi+1都是终结符 then
Xi = Xi+1
if i<= n-2, Xi和Xi+2 是终结符, 但Xi+1 为非终结符 then
Xi = Xi+2
if Xi为终结符, Xi+1为非终结符 then
for FirstVT 中的每个元素 a do
Xi < a ;
if Xi为非终结符, Xi+1为终结符 then
for LastVT 中的每个元素 a do
a > Xi+1 ;
6、构造总控程序
算法描述如下:
stack S;
k = 1; //符号栈S的使用深度
S[k] = ‘#’
REPEAT
把下一个输入符号读进a中;
If S[k]
VT then j = k else j = k-1;
While S[j] > a do
Begin
Repeat
Q = S[j];
if S[j-1]
VT then j = j-1 else j = j-2
until S[j] < Q;
把S[j+1]…S[k]归约为某个N,并输出归约为哪个符号;
K = j+1;
S[k] = N;
end of while
if S[j] < a or S[j] = a then
begin k = k+1; S[k] = a end
else error //调用出错诊察程序
until a = ‘#’
7、对给定的表达式,给出准确与否的分析过程
8、给出表达式的计算结果。(本步骤 可选作)
三、实验报告
1.实验步骤
设计思路:同实验二的设计思路,默认终结符i表示数字,当输入数字通过input_process(test)将输入的数字转换为终结符i(仅限0~9的数字,这是不够好的地方);对于形如“A->B”这样一个产生式的保存,依然采用如下的结构体表示一个语法产生式。
typedef struct {
char left; // 产生式左边,非终结符
int rcount; // 右边式子数量
char right[200][200];
int right_length[200]; // 每个产生式右侧的长度
int firstvtcount;
int lastvtcount;
char FirstVT[200]; // FirstVT集合
char LastVT[200]; // LastVT集合
} grammar;
grammar gramSet[200]; // 产生式集(直接输入算符优先文法)
gramSet代表产生式集合,每一条产生式处理以后存放在gramSet中。先对读入的每一条产生式进行处理,依旧是“->”左边的是非终结符,对应成员left。“->”右边的是产生式右边的内容,如果遇到了“|”则代表后面的符号是该非终结符的又一条产生式,它对应rcount的数量,right数组是一个二维数组,如果一个非终结符对应有多个产生式,那么right数组则对应这个非终结符的产生式右部,该数组的长度是right_length;每个产生式的左边是非终结符,都对应自己的FirstVT集和LastVT集,其中firstvtcount和lastvtcount又对应了它们各自的数量。
程序的开始输入相应的文法通过grammar_process函数处理,将文法中的终结符和非终结符分别记录下来,便于求FirstVT集、LastVT集、算符优先分析表等。对于这些集和的运算,总体思路都是一一扫描每一条产生式集合的前两个字符(或者末尾两个字符)。通过FirstVT集和LastVT集来确定算符优先分析表table。
对于算符优先文法,有一个特殊的性质是,不会出现连续两个非终结符连续出现,这点对我的思路启发很大。首先建立一个char table[200][200],table数组根据对产生式右边连续两个字符的性质来决定,包括了前一个字符是终结符后一个字符是非终结符(那么接下来第三个字符就一定是终结符)、前一个字符是非终结符后一个字符是终结符、连续两个字符都是终结符,然后把文法的开始符号和“#”做相应的处理即可,赋值即可。
接下来是主要代码:
(1)FirstVT集合
void firstvt(char sym) {
int i = get_nonter_index(sym); // 找到了该非终结符的位置
// 处理该非终结符对应的每条产生式右部的前两个字符
for (int j = 0; j < gramSet[i].rcount; j++) {
char ch = gramSet[i].right[j][0]; // 产生式右部第一个字符ch
if (istersym(ch)) {
// 符合A->a...的形式,将a加入FirstVT(A)中
if (!char_isexist_firstvt(ch, i)) {
gramSet[i].FirstVT[gramSet[i].firstvtcount++] = ch;
}
} else {
// 符合A->Ba...的形式,将a加入FirstVT(A)中,把FirstVT(B)中元素加入到FirstVT(A)中
// 算符优先文法非终结符不会成对出现,因此gramSet[i].right[j][1]是终结符
if (gramSet[i].right[j][1] != '\0' && !char_isexist_firstvt(gramSet[i].right[j][1], i))
gramSet[i].FirstVT[gramSet[i].firstvtcount++] = gramSet[i].right[j][1];
int p = get_nonter_index(ch); // 找到该非终结符在产生式集合的位置
if (!gramSet[p].firstvtcount)
firstvt(ch);
// 递归先求FirstVT(ch),然后FirstVT(ch)加到FirstVT(sym)
addfirstvt(sym, i, ch, p);
}
}
}
(2)LastVT集合
void lastvt(char sym) {
int i = get_nonter_index(sym); // 找到了该非终结符的位置
// 处理该非终结符对应的每条产生式右部的末两个字符
for (int j = 0; j < gramSet[i].rcount; j++) {
int length = strlen(gramSet[i].right[j]); // 产生式右部长度
char ch = gramSet[i].right[j][length - 1]; // 产生式右部最后一个字符ch
if (istersym(ch)) {
// 符合A->...a的形式,将a加入LastVT(A)中
if (!char_isexist_lastvt(ch, i)) {
gramSet[i].LastVT[gramSet[i].lastvtcount++] = ch;
}
} else {
// 符合A->...B(其实也是A->...aB)的形式,将a加入LastVT(A)中,把LastVT(B)中元素加入到LastVT(A)中
char ter = gramSet[i].right[j][length - 2]; // 这个非终结符前面的一定是终结符a
if (!char_isexist_lastvt(ter, i))
gramSet[i].LastVT[gramSet[i].lastvtcount++] = ter;
int p = get_nonter_index(ch); // 找到该非终结符在产生式集合的位置
if (!gramSet[p].lastvtcount)
lastvt(ch);
addlastvt(sym, i, ch, p);
}
}
}
(3)构建算符优先分析表
void create_OPA() {
for (int i = 0; i < gramcount; i++) {
for (int j = 0; j < gramSet[i].rcount; j++) {
int length = strlen(gramSet[i].right[j]);
if (length >= 2) {
// 起码每条产生式右部的长度超过2才有比较意义
for (int k = 0; k < length - 1; k++) {
char pre = gramSet[i].right[j][k]; // 当前字符
char after = gramSet[i].right[j][k + 1]; // 下一个字符
int pre_index, after_index; // 记录二者的下标
// 记录他们是否是终结符
int pre_ister = istersym(pre);
int after_ister = istersym(after);
if (pre_ister && after_ister) {
// 若连续两个终结符x1x2,则x1'='x2
pre_index = get_terminal_index(pre);
after_index = get_terminal_index(after);
table[pre_index][after_index] = '=';
}
if (pre_ister && !after_ister) {
// 若终结符x1非终结符x2,则x1'<'FirstVT(x2)
pre_index = get_terminal_index(pre);
after_index = get_nonter_index(after);
for (int l = 0; l < gramSet[after_index].firstvtcount; l++) {
// 获取firstvt集中元素的下标
char first = gramSet[after_index].FirstVT[l];
int first_index = get_terminal_index(first);
table[pre_index][first_index] = '<';
}
}
if (!pre_ister && after_ister) {
// 若非终结符x1终结符x2,则LastVT(x1)'>'x2
pre_index = get_nonter_index(pre);
after_index = get_terminal_index(after);
for (int l = 0; l < gramSet[pre_index].lastvtcount; l++) {
// 获取lastvt集中元素的下标
char last = gramSet[pre_index].LastVT[l];
int last_index = get_terminal_index(last);
table[last_index][after_index] = '>';
}
}
char temp = gramSet[i].right[j][k + 2]; // 下下一个元素
if ((k < length - 2) && pre_ister && !after_ister && istersym(temp)) {
// 若终结符x1非终结符x2终结符x3,则x1'='x3
pre_index = get_terminal_index(pre);
table[pre_index][get_terminal_index(temp)] = '=';
}
}
}
}
}
// 文法开始符号和#的优先关系 1:#'<'FirstVT(E)
int special_index = get_terminal_index('#');
for (int l = 0; l < gramSet[0].firstvtcount; l++) {
// 获取文法起始符号的firstvt集中元素的下标
char first = gramSet[0].FirstVT[l];
int first_index = get_terminal_index(first);
table[special_index][first_index] = '<';
}
// 文法开始符号和#的优先关系 2:LastVT(E)'>'#
for (int l = 0; l < gramSet[0].lastvtcount; l++) {
// 获取文法起始符号的flastvt集中元素的下标
char last = gramSet[0].LastVT[l];
int last_index = get_terminal_index(last);
table[last_index][special_index] = '>';
}
// #'='#
table[special_index][special_index] = '=';
}
(4)总控程序
void main_control() {
int i = 0, flag = 1; // 输入串位置
char ch, top, relation; // 当前字符、栈顶字符、优先级
char content[200], test2[200];
stack* s = (stack*)malloc(sizeof(stack));
initstack(s);
push(s, '#'); // 栈底放入#
printf("S栈\t\t优先关系\t\t当前符号\t输入串\t\t动作");
printf("\n#\t\t\t\t\t\t\t%s\t\t%s", test, action[0]);
while (flag) {
ch = test[i];
if (ch == '#' && (get_num_stack(s, 1) == '#' ||
(get_num_stack(s, 1) == 'N' && get_num_stack(s, 2) == '#'))) {
flag = 0; // 成功归约结束标志
}
strcpy(content, get_stack(s));
strcpy(test2, test + i);
top = get_num_stack(s, 1);
int top_index; // 终结符栈顶
if (top == 'N') {
top = get_num_stack(s, 2);
top_index = get_terminal_index(top);
} else {
top_index = get_terminal_index(top);
}
relation = table[top_index][get_terminal_index(ch)]; // 只关心栈顶的终结符和当前输入串顶
printf("\n%s\t\t\t%c\t\t%c\t\t%s\t\t", content, relation, ch, test2);
if ((relation == '<' || relation == '=')) {
// 只要是'<'或者'='就先入栈
push(s, ch);
i++;
printf("%s", action[1]);
} else if (relation == '>') { // 向前找到最近的'<'为止
if (top == '*' || top == '/' || top == '+' || top == '-') {
// 归约运算符时前后必须是N
if (get_num_stack(s, 1) == 'N' && get_num_stack(s, 3) == 'N') {
int pop_num = 3;
while (pop_num--) {
pop(s);
}
printf("%s", action[2]);
} else {
printf("\n运算符归约过程出错!");
break;
}
} else if (top == ')') {
if (get_num_stack(s, 2) == 'N' && get_num_stack(s, 3) == '(') {
int pop_num = 3;
while (pop_num--) {
pop(s);
}
printf("%s", action[2]);
} else {
printf("\n括号归约过程出错!");
break;
}
} else {
pop(s);
printf("%s", action[2]);
}
push(s, 'N');
} else if (relation == '\0') {
printf("\n%s\t\t\t%c\t\t%c\t\t%s\t\t%s", content, relation, ch, test2, action[3]);
printf("\n归约过程出错,两个算符之间没有优先关系!");
break;
}
if (!flag) {
printf("\n##\t\t\t\t\t\t\t\t\t%s", action[4]);
printf("\n正确!");
}
}
free(s->base);
free(s);
}
(5)实验结果
请输入文法: 以'#'结束
E->E+T|E-T|T
T->T*F|T/F|F
F->(E)|i
#
(默认终结符i代表数字),输入串(以'#'结束)为:
1+2-3+(*4/5)#
FirstVT集如下:
FirstVT(E): + - * / ( i
FirstVT(T): * / ( i
FirstVT(F): ( i
LastVT集如下:
LastVT(E): + * ) i / -
LastVT(T): * ) i /
LastVT(F): ) i
算符优先表如下:
+ | - | * | / | ( | ) | i | # | |
+ | > | > | < | < | < | > | < | > |
- | > | > | < | < | < | > | < | > |
* | > | > | > | > | < | > | < | > |
/ | > | > | > | > | < | > | < | > |
( | < | < | < | < | < | = | < | none |
) | > | > | > | > | none | > | none | > |
i | > | > | > | > | none | > | none | > |
# | < | < | < | < | < | none | < | = |
算符优先分析步骤如下:
S栈 优先关系 当前符号 输入串 动作
# i+i-i+(*i/i)# 预备
# < i i+i-i+(*i/i)# 移进
#i > + +i-i+(*i/i)# 归约
#N < + +i-i+(*i/i)# 移进
#N+ < i i-i+(*i/i)# 移进
#N+i > - -i+(*i/i)# 归约
#N+N > - -i+(*i/i)# 归约
#N < - -i+(*i/i)# 移进
#N- < i i+(*i/i)# 移进
#N-i > + +(*i/i)# 归约
#N-N > + +(*i/i)# 归约
#N < + +(*i/i)# 移进
#N+ < ( (*i/i)# 移进
#N+( < * *i/i)# 移进
#N+(* < i i/i)# 移进
#N+(*i > / /i)# 归约
#N+(*N > / /i)# 归约
运算符归约过程出错!
同样文法,输入串为:(1+2)/3+4-(5+6/7)#
算符优先分析步骤如下:
S栈 优先关系 当前符号 输入串 动作
# (i+i)/i+i-(i+i/i)# 预备
# < ( (i+i)/i+i-(i+i/i)# 移进
#( < i i+i)/i+i-(i+i/i)# 移进
#(i > + +i)/i+i-(i+i/i)# 归约
#(N < + +i)/i+i-(i+i/i)# 移进
#(N+ < i i)/i+i-(i+i/i)# 移进
#(N+i > ) )/i+i-(i+i/i)# 归约
#(N+N > ) )/i+i-(i+i/i)# 归约
#(N = ) )/i+i-(i+i/i)# 移进
#(N) > / /i+i-(i+i/i)# 归约
#N < / /i+i-(i+i/i)# 移进
#N/ < i i+i-(i+i/i)# 移进
#N/i > + +i-(i+i/i)# 归约
#N/N > + +i-(i+i/i)# 归约
#N < + +i-(i+i/i)# 移进
#N+ < i i-(i+i/i)# 移进
#N+i > - -(i+i/i)# 归约
#N+N > - -(i+i/i)# 归约
#N < - -(i+i/i)# 移进
#N- < ( (i+i/i)# 移进
#N-( < i i+i/i)# 移进
#N-(i > + +i/i)# 归约
#N-(N < + +i/i)# 移进
#N-(N+ < i i/i)# 移进
#N-(N+i > / /i)# 归约
#N-(N+N < / /i)# 移进
#N-(N+N/ < i i)# 移进
#N-(N+N/i > ) )# 归约
#N-(N+N/N > ) )# 归约
#N-(N+N > ) )# 归约
#N-(N = ) )# 移进
#N-(N) > # # 归约
#N-N > # # 归约
#N = # # 移进
## 结束
正确!
同样文法,输入串为:((1-2)/3+4#
算符优先分析步骤如下:
算符优先分析步骤如下:
S栈 优先关系 当前符号 输入串 动作
# ((i-i)/i+i# 预备
# < ( ((i-i)/i+i# 移进
#( < ( (i-i)/i+i# 移进
#(( < i i-i)/i+i# 移进
#((i > - -i)/i+i# 归约
#((N < - -i)/i+i# 移进
#((N- < i i)/i+i# 移进
#((N-i > ) )/i+i# 归约
#((N-N > ) )/i+i# 归约
#((N = ) )/i+i# 移进
#((N) > / /i+i# 归约
#(N < / /i+i# 移进
#(N/ < i i+i# 移进
#(N/i > + +i# 归约
#(N/N > + +i# 归约
#(N < + +i# 移进
#(N+ < i i# 移进
#(N+i > # # 归约
#(N+N > # # 归约
#(N # # 归约
#(N # # 出错
归约过程出错,两个算符之间没有优先关系!
(6)完整代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char left; // 产生式左边,非终结符
int rcount; // 右边式子数量,如果遇到‘|’就会转到该非终结符的下一条
char right[200][200];
int right_length[200]; // 每个产生式右侧的长度
int firstvtcount;
int lastvtcount;
char FirstVT[200]; // FirstVT集合
char LastVT[200]; // LastVT集合
} grammar;
grammar gramSet[200];
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 table[200][200]; // 算符优先分析表
char test[500]; // 要匹配的字符串
char action[6][20] = {"预备", "移进", "归约", "出错", "结束"}; // 算符优先分析动作
void read(); // 读入数据
void input_process(char* input); // 处理输入串中的数字
void initstack(stack* s); // 初始化栈
void push(stack* s, char x); // 入栈
char get_num_stack(stack* s, int num); // 获得栈内元素
void pop(stack* s); // 出栈
int istersym(char ch); // 是否是终结符
void grammar_process(char ch); // 对产生式处理
void firstvt(char sym); // 求sym的firstvt集
void addfirstvt(char sym, int sym_index, char ch, int ch_index); // firstvt(ch)并入firstvt(sym)
int char_isexist_firstvt(char ch, int sym_index); // ch是否在对应的FirstVT中
void lastvt(char sym); // 求sym的Lastvt集
void addlastvt(char sym, int sym_index, char ch, int ch_index); // lastvt(ch)并入lastvt(sym)
int char_isexist_lastvt(char ch, int sym_index); // ch是否在对应的LastVT中
void create_OPA(); // 创建算符优先分析表
int get_terminal_index(char sym); // 获取终结符在表中的索引
int get_nonter_index(char sym); // 获取非终结符在表中的索引
void show(); // 显示算符优先分析表
void main_control(); // 主控程序
int main() {
printf("请输入文法: 以'#'结束\n");
read();
startsym = gramSet[0].left; // 开始符号
printf("(默认终结符i代表数字),输入串(以'#'结束)为:\n");
scanf("%s", test);
input_process(test); // 对输入串处理,把输入的数字变为终结符i,归到test中
for (int i = 0; i < gramcount; i++) {
firstvt(gramSet[i].left);
}
for (int i = 0; i < gramcount; i++) {
lastvt(gramSet[i].left);
}
printf("\n");
printf("FirstVT集如下:\n");
for (int i = 0; i < gramcount; i++) {
printf("FirstVT(%c): ", gramSet[i].left);
for (int j = 0; j < gramSet[i].firstvtcount; j++)
printf("%c ", gramSet[i].FirstVT[j]);
printf("\n");
}
printf("\n");
printf("LastVT集如下:\n");
for (int i = 0; i < gramcount; i++) {
printf("LastVT(%c): ", gramSet[i].left);
for (int j = 0; j < gramSet[i].lastvtcount; j++)
printf("%c ", gramSet[i].LastVT[j]);
printf("\n");
}
printf("\n");
printf("算符优先表如下:\n");
create_OPA();
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++) {
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].right_length[gramSet[gramcount].rcount] = strlen(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) { // 将非终结符和终结符分别放在对应的数组中
int is_exist = 0;
if (!istersym(ch)) {
for (int i = 0; i < nontercount; i++) {
if (ch == nonter[i]) {
is_exist = 1;
break; // 已在非终结符集中
}
}
if (is_exist == 0) {
nonter[nontercount++] = ch;
}
} else {
for (int i = 0; i < tersymcount; i++) {
if (ch == terSymbol[i]) {
is_exist = 1;
break; // 已在终结符集中
}
}
if (is_exist == 0) {
terSymbol[tersymcount++] = ch;
}
}
}
void input_process(char* input) { // 将输入串中的数字改成i(默认终结符i代表数字)
char* copy = strcpy(copy, input);
int i = 0;
while (copy[i] != '\0') {
if (copy[i] >= '0' && copy[i] <= '9') {
// 这个字符是数字的话,就把它改成i
copy[i] = 'i';
}
i++;
}
strcpy(input, copy); // 将处理后的字符串复制回原始字符串
}
void firstvt(char sym) {
int i = get_nonter_index(sym); // 找到了该非终结符的位置
// 处理该非终结符对应的每条产生式右部的前两个字符
for (int j = 0; j < gramSet[i].rcount; j++) {
char ch = gramSet[i].right[j][0]; // 产生式右部第一个字符ch
if (istersym(ch)) {
// 符合A->a...的形式,将a加入FirstVT(A)中
if (!char_isexist_firstvt(ch, i)) {
gramSet[i].FirstVT[gramSet[i].firstvtcount++] = ch; // 没存过过就加入
}
} else {
// 符合A->Ba...的形式,将a加入FirstVT(A)中,把FirstVT(B)中元素加入到FirstVT(A)中
// 算符优先文法非终结符不会成对出现,因此gramSet[i].right[j][1]是终结符
if (gramSet[i].right[j][1] != '\0' && !char_isexist_firstvt(gramSet[i].right[j][1], i))
gramSet[i].FirstVT[gramSet[i].firstvtcount++] = gramSet[i].right[j][1];
int p = get_nonter_index(ch); // 找到该非终结符在产生式集合的位置
if (!gramSet[p].firstvtcount)
firstvt(ch);
// 递归先求FirstVT(ch),然后FirstVT(ch)加到FirstVT(sym)
addfirstvt(sym, i, ch, p);
}
}
}
void addfirstvt(char sym, int sym_index, char ch, int ch_index) {
for (int l = 0; l < gramSet[ch_index].firstvtcount; l++) {
int found = 0; // 标记是否找到重复元素
for (int k = 0; k < gramSet[sym_index].firstvtcount; k++) {
if (gramSet[sym_index].FirstVT[k] == gramSet[ch_index].FirstVT[l]) {
found = 1;
break; // 找到重复元素,不再继续检查
}
}
if (!found) {
gramSet[sym_index].FirstVT[gramSet[sym_index].firstvtcount++] = gramSet[ch_index].FirstVT[l];
}
}
}
int char_isexist_firstvt(char ch, int sym_index) {
int flag = 0;
for (int k = 0; k < gramSet[sym_index].firstvtcount; k++) {
if (gramSet[sym_index].FirstVT[k] == ch) {
flag = 1;
break;
}
}
return flag;
}
void lastvt(char sym) {
int i = get_nonter_index(sym); // 找到了该非终结符的位置
// 处理该非终结符对应的每条产生式右部的末两个字符
for (int j = 0; j < gramSet[i].rcount; j++) {
int length = strlen(gramSet[i].right[j]); // 产生式右部长度
char ch = gramSet[i].right[j][length - 1]; // 产生式右部最后一个字符ch
if (istersym(ch)) {
// 符合A->...a的形式,将a加入LastVT(A)中
if (!char_isexist_lastvt(ch, i)) {
gramSet[i].LastVT[gramSet[i].lastvtcount++] = ch; // 没存过过就加入
}
} else {
// 符合A->...B(其实也是A->...aB)的形式,将a加入LastVT(A)中,把LastVT(B)中元素加入到LastVT(A)中
char ter = gramSet[i].right[j][length - 2]; // 这个非终结符前面的一定是终结符a
if (!char_isexist_lastvt(ter, i))
gramSet[i].LastVT[gramSet[i].lastvtcount++] = ter;
int p = get_nonter_index(ch); // 找到该非终结符在产生式集合的位置
if (!gramSet[p].lastvtcount)
lastvt(ch);
addlastvt(sym, i, ch, p);
}
}
}
void addlastvt(char sym, int sym_index, char ch, int ch_index) {
for (int l = 0; l < gramSet[ch_index].lastvtcount; l++) {
int found = 0; // 标记是否找到重复元素
for (int k = 0; k < gramSet[sym_index].lastvtcount; k++) {
if (gramSet[sym_index].LastVT[k] == gramSet[ch_index].LastVT[l]) {
found = 1;
break; // 找到重复元素,不再继续检查
}
}
if (!found) {
gramSet[sym_index].LastVT[gramSet[sym_index].lastvtcount++] = gramSet[ch_index].LastVT[l];
}
}
}
int char_isexist_lastvt(char ch, int sym_index) {
int flag = 0;
for (int k = 0; k < gramSet[sym_index].lastvtcount; k++) {
if (gramSet[sym_index].LastVT[k] == ch) {
flag = 1;
break;
}
}
return flag;
}
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_OPA() {
for (int i = 0; i < gramcount; i++) {
for (int j = 0; j < gramSet[i].rcount; j++) {
int length = strlen(gramSet[i].right[j]);
if (length >= 2) {
// 起码每条产生式右部的长度超过2才有比较意义
for (int k = 0; k < length - 1; k++) {
char pre = gramSet[i].right[j][k]; // 当前字符
char after = gramSet[i].right[j][k + 1]; // 下一个字符
int pre_index, after_index; // 记录二者的下标
// 记录他们是否是终结符
int pre_ister = istersym(pre);
int after_ister = istersym(after);
if (pre_ister && after_ister) {
// 若连续两个终结符x1x2,则x1'='x2
pre_index = get_terminal_index(pre);
after_index = get_terminal_index(after);
table[pre_index][after_index] = '=';
}
if (pre_ister && !after_ister) {
// 若终结符x1非终结符x2,则x1'<'FirstVT(x2)
pre_index = get_terminal_index(pre);
after_index = get_nonter_index(after);
for (int l = 0; l < gramSet[after_index].firstvtcount; l++) {
// 获取firstvt集中元素的下标
char first = gramSet[after_index].FirstVT[l];
int first_index = get_terminal_index(first);
table[pre_index][first_index] = '<';
}
}
if (!pre_ister && after_ister) {
// 若非终结符x1终结符x2,则LastVT(x1)'>'x2
pre_index = get_nonter_index(pre);
after_index = get_terminal_index(after);
for (int l = 0; l < gramSet[pre_index].lastvtcount; l++) {
// 获取lastvt集中元素的下标
char last = gramSet[pre_index].LastVT[l];
int last_index = get_terminal_index(last);
table[last_index][after_index] = '>';
}
}
char temp = gramSet[i].right[j][k + 2]; // 下下一个元素
if ((k < length - 2) && pre_ister && !after_ister && istersym(temp)) {
// 若终结符x1非终结符x2终结符x3,则x1'='x3
pre_index = get_terminal_index(pre);
table[pre_index][get_terminal_index(temp)] = '=';
}
}
}
}
}
// 文法开始符号和#的优先关系 1:#'<'FirstVT(E)
int special_index = get_terminal_index('#');
for (int l = 0; l < gramSet[0].firstvtcount; l++) {
// 获取文法起始符号的firstvt集中元素的下标
char first = gramSet[0].FirstVT[l];
int first_index = get_terminal_index(first);
table[special_index][first_index] = '<';
}
// 文法开始符号和#的优先关系 2:LastVT(E)'>'#
for (int l = 0; l < gramSet[0].lastvtcount; l++) {
// 获取文法起始符号的flastvt集中元素的下标
char last = gramSet[0].LastVT[l];
int last_index = get_terminal_index(last);
table[last_index][special_index] = '>';
}
// #'='#
table[special_index][special_index] = '=';
}
void show() { // 显示算符优先分析表
printf("\t");
int i, j;
for (i = 0; i < tersymcount; i++) {
printf("%c\t", terSymbol[i]); // 打印表行
}
printf("\n");
while (i--)
printf("---------");
for (i = 0; i < tersymcount; i++) {
printf("\n%c\t", terSymbol[i]);
for (j = 0; j < tersymcount; j++) {
if (table[i][j] == '\0')
printf("none\t");
else
printf("%c\t", table[i][j]);
}
printf("\n");
while (j--)
printf("---------");
}
}
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_num_stack(stack* s, int num) {
if (num >= 1 && num <= s->top - s->base) {
return *(s->top - num); // 返回栈第num个元素
} else {
return '\0';
}
}
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 i = 0, flag = 1; // 输入串位置
char ch, top, relation; // 当前字符、栈顶字符、优先级
char content[200], test2[200];
stack* s = (stack*)malloc(sizeof(stack));
initstack(s);
push(s, '#'); // 栈底放入#
printf("S栈\t\t优先关系\t\t当前符号\t输入串\t\t动作");
printf("\n#\t\t\t\t\t\t\t%s\t\t%s", test, action[0]);
while (flag) {
ch = test[i];
if (ch == '#' && (get_num_stack(s, 1) == '#' ||
(get_num_stack(s, 1) == 'N' && get_num_stack(s, 2) == '#'))) {
flag = 0; // 成功归约结束标志
}
strcpy(content, get_stack(s));
strcpy(test2, test + i);
top = get_num_stack(s, 1);
int top_index; // 终结符栈顶
if (top == 'N') {
top = get_num_stack(s, 2);
top_index = get_terminal_index(top);
} else {
top_index = get_terminal_index(top);
}
relation = table[top_index][get_terminal_index(ch)]; // 只关心栈顶的终结符和当前输入串顶
printf("\n%s\t\t\t%c\t\t%c\t\t%s\t\t", content, relation, ch, test2);
if ((relation == '<' || relation == '=')) {
// 只要是'<'或者'='就先入栈
push(s, ch);
i++;
printf("%s", action[1]);
} else if (relation == '>') { // 向前找到最近的'<'为止
if (top == '*' || top == '/' || top == '+' || top == '-') {
// 归约运算符时前后必须是N
if (get_num_stack(s, 1) == 'N' && get_num_stack(s, 3) == 'N') {
int pop_num = 3;
while (pop_num--) {
pop(s);
}
printf("%s", action[2]);
} else {
printf("\n运算符归约过程出错!");
break;
}
} else if (top == ')') {
if (get_num_stack(s, 2) == 'N' && get_num_stack(s, 3) == '(') {
int pop_num = 3;
while (pop_num--) {
pop(s);
}
printf("%s", action[2]);
} else {
printf("\n括号归约过程出错!");
break;
}
} else {
pop(s);
printf("%s", action[2]);
}
push(s, 'N');
} else if (relation == '\0') {
printf("\n%s\t\t\t%c\t\t%c\t\t%s\t\t%s", content, relation, ch, test2, action[3]);
printf("\n归约过程出错,两个算符之间没有优先关系!");
break;
}
if (!flag) {
printf("\n##\t\t\t\t\t\t\t\t\t%s", action[4]);
printf("\n正确!");
}
}
free(s->base);
free(s);
}
2.实验时发现的问题及解决过程
对于总控程序,根据已经求得的算符优先分析表,建立一个分析栈并将‘#’压入栈中,接着,通过比较输入串指针指向的符号和分析栈的栈顶指向的符号的算符优先级选择归约或者移进即可。但是要在里面多加一些条件充实各种不同的输入情况,在这里算符优先文法句型的特点起到了至关重要的作用,即算符优先文法不会出现两个非终结符连续出现的情况,即不会出现"NN"的情况。举个例子,如果遇到类似"(*4/5)#"的句型,此时"/"为输入串顶时将要归约操作,栈中内容为"(*N",而"*"左右不全是"N",因此归约出错。而我遇到的问题是:一开始我的主控程序只有归约操作(没有归约结束后将N压入栈的操作),虽然这种做法节省了栈空间,也提高了效率,但是我思来想去没有想到好的办法解决归约运算符号时该如何确定运算符前后是否为数字的问题,因此只能改为将N压入栈中,以N作为归约串,因而解决了确定归约运算符的问题。