【问题描述】
给定以下文法G[E]:
E->TE′
E′->+TE′| ε
T->FT′
T′->*FT′| e
F->a | (E)
构建LL(1)预测分析表,并用C++编程实现LL(1)语法分析器
【输入形式】
一个句子
【输出形式】
若句子语法正确,输出"Syntax analysis is right"
若语法语法错误,输出"Error on syntax analysis"
注:输出不包括引号
【样例输入】
a+a*a
【样例输出】
Syntax analysis is right
【构建基于LL(1)文法的预测分析表】
先算出FIRST集和FOLLOW集
然后算出SELECT集
最后构建预测分析表
【程序思想】
用一维数组VT和VN存储终结符集和非终结符集,用二维字符串数组map存储预测分析表,写一个find函数用于匹配每个终结符和非终结符所在的行和列,用于后面在分析表中找产生式。用分析栈 st来协助分析,栈底要先放入终结符‘#’和开始符‘E’,不然没有办法分析正确。为了简化代码,我们用X和Y分别替代E’和T’,^代替ε,空格代表没有产生式。初始化一个全局变量index用于指示当前分析到输入字符串的哪一位,对于输入字符串中的每一位,分为终结符和非终结符两种情况,如果是终结符,判断是否是‘#’,如果是且栈顶也是 ‘#’,则该语句语法正确,如果栈顶和当前字符相同但不是‘#’,栈顶弹出,index右移,判断下一个字符。如果是非终结符,就去预测分析表map中寻找栈顶和当前字符的产生式,如果找不到则分析错误,如果找到了,栈顶弹出,然后将产生式右部逆序入栈,进行下一轮分析。最后,记得输入的语句末尾要加上‘#’。
【代码实现】
#include<iostream>
#include<stack>
#include<vector>
using namespace std;
string s;
int index = 0;
//为了简化代码,我们用X和Y分别替代E'和T',^代替ε
char VN[5] = { 'E','X','T','Y','F' };
char VT[6] = { 'a','+','*','(',')','#' };
string map[6][7] = {
" ","a","+","*","(",")","#",
"E","TX"," "," ","TX"," "," ",
"X"," ","+TX"," "," ","^","^",
"T","FY"," "," ","FY"," "," ",
"Y"," ","^","*FY"," ","^","^",
"F","a"," "," ","(E)"," "," "
};
int find(char c) {
switch (c) {
case 'E':
return 1;
case 'X':
return 2;
case 'T':
return 3;
case 'Y':
return 4;
case 'F':
return 5;
case 'a':
return 1;
case '+':
return 2;
case '*':
return 3;
case '(':
return 4;
case ')':
return 5;
case '#':
return 6;
default:
return 0;
}
}
void LL()
{
stack<char> st;
st.push('#');
st.push('E');
int i = 0;
int temp;//如果找到了终结符且匹配正确则标记一下不用继续后续步骤直接开始下一轮
while (true)
{
char c = st.top();
temp = 0;
//栈顶是终结符
for (int it = 0; it < 6; it++)
{
if (VT[it] == c)
{
if (s[i] == '#' && c == '#')
{
cout << "Syntax analysis is right";
return;
}
if (s[i] == c)
{
st.pop();
i++;
temp = 1;
break;
}
else
{
cout << "Error on syntax analysis";
return;
}
}
}
//栈顶是非终结符
if (temp == 1)
continue;
int n1 = find(c);
int n2 = find(s[i]);
st.pop();
if (map[n1][n2] == " ")
{
cout << "Error on syntax analysis";
return;
}
int len = map[n1][n2].length();
for (int k = len - 1; k >= 0; k--)
{
if (map[n1][n2][k] == '^')
continue;
st.push(map[n1][n2][k]);//逆序进栈
}
}
}
int main()
{
cin >> s;
int length = s.length();
s[length] = '#';
LL();
return 0;
}
/*
a+a*a
*/
【遇到的问题】
(1) 由于粗心,在初始化map的时候,我输入时忘记了自己已经将E’替换成X,T’替换成Y,导致有的产生式写错,还有产生式前面的加号被我看漏了,后来调试程序才找到错误源头。
(2) 还是由于粗心,我在写终结符的情况的时候,使用for循环遍历终结符数组时,将后面第二部分非终结符的情况也写入了这个for循环后面,而且由于样例输入前半部分字符非常巧合地找到了对的产生式,导致我debug了很久(在纸上演练了好几遍分析栈的过程)才发现这个错误。
最后总结一下,这次的语法分析实验总的来说让我对计算机编译程序时的语法分析有了更深刻的了解,也提升了写LL(1)预测分析表的熟练度,收获很多。当然了,也再一次提醒自己,写代码要细心细心再细心一点,粗心的程序很容易浪费很多时间在调试的过程上。