最近在看编译原理,涉及到了词法分析和语法分析等阶段,故此自己尝试着用C++实现了对表达式形如:expression=x1-x2*c的词法分析和语法分析,预想的运行结果为:
符号表: | 分析树:
expression 标识符 | =
= 操作符 | expression -
x1 标识符 | x1 *
- 操作符 | x2 c
x2 标识符 |
* 操作符 |
c 标识符
思路:大体遵循着编译原理中提到的词法和语法分析步骤,即先对表达式进行区分为操作符或标志符,将其存放在符号表中,而后从符号表中识别符号类别和操作符优先级存放在二叉树中。
自定义类型:
typedef int ExpType; //符号表中类别标志:1为标识符,2为操作符
typedef string ElemType; //表达式中的符号类型
词法分析中符号表的构造如下:
typedef struct wordsTable //词法分析中存储数据--符号表
{
ElemType w; //符号
ExpType t; //类型
struct wordsTable *next;
};
涉及到的操作有:
int wordsTable_Inital(wordsTable* ws){ }; //符号表的初始化
bool Add_Table_at_end(wordsTable* w,wordsTable* node_to_add){ };//在符号表尾部插入节点
int PrintTable(wordsTable* ws){ }; //显示符号表
int DivisionExp(ElemType arrary[Size],string Exp){}; //识别表达式,将其存放在字符串数组中
int InsertSignTable(ElemType arrary[Size],int N,wordsTable* ws){ };//从字符串数组中读取数据并插入符号表中
语法分析中二叉树的构造如下:
typedef struct ParsingTreeNode //语法分析中存储数据--语法树
{
ElemType Oper; //符号表中各符号在分析树中的表示形式
struct ParsingTreeNode *left; //左节点
struct ParsingTreeNode *right; //右节点
struct ParsingTreeNode *father; //父节点
};
涉及到的操作有:
//初始化分析树
int ParsingTree_Inital(ParsingTreeNode* root) {};
//中排序分析树显示 (丛网上找到的竖向的树状排序算法)
void PrintTree(ParsingTreeNode* Root,int Layer){ };
//显示分析树
int PrintParsing(ParsingTreeNode* ROOT) {};
//查找出分析树中右节点为空的操作符节点,用于就标志符的插入操作
ParsingTreeNode* Tree_FindOper(ParsingTreeNode* Root){};
//查找出分析树中自根节点最右的操作符节点,用于操作符的插入操作
ParsingTreeNode* Tree_FindOper1(ParsingTreeNode* Root){};
//查找出分析树中自节点Op的父节点始操作符优先级等于NewP的节点
ParsingTreeNode* Tree_FindOper2(ParsingTreeNode* Op,ParsingTreeNode* NewP){};
//将新节点进行分析并插入分析树中
int Insert_ParsingTree(ParsingTreeNode* ROOT,ParsingTreeNode* NewP,ExpType T){};
主函数如下:
int main()
{
int i;
wordsTable* ws=new wordsTable;
wordsTable* q;
ParsingTreeNode* root=new ParsingTreeNode;
string a;
string arrary[Size];
int N;
wordsTable_Inital(ws);
cout<<"请输入表达式\n"<<endl;
cin>>a;
getchar();
N=DivisionExp(arrary,a); //识别表达式,存放在字符串数组中
InsertSignTable(arrary,N,ws);//从字符串中将数据插入符号表中
PrintTable(ws); //显示符号表
ParsingTree_Inital(root); //分析树初始化
q=ws->next;
while(q)
{
ParsingTreeNode* NewP=new ParsingTreeNode;
NewP->Oper=q->w;
NewP->left=NULL;
NewP->right=NULL;
NewP->father=NULL;
Insert_ParsingTree(root,NewP,q->t);
q=q->next;
}
PrintParsing(root); //分析树打印
getchar();
return 0;
}
运行结果为:
分析:
整个过程中,主要在树的操作上(生疏的缘故)以及如何将符号表中的数据存放在二叉树的花费了不少时间。在此,对二叉树的构造思路进行分析。
假设 1、二叉树以一个空的头节点Root标识,将符号表中的数据以二叉树根节点的右节点开始插入,故实际操作中根节点应为Root的右节点
假设 2、标识符的左右节点为NULL,只有操作符的左右节点分别对应着该操作符相应的操作数
假设 3、不考虑括号类的操作符标志
首先根据符号表中数据创建一个新节点,然后对符号的类型进行判断
A、当为标识符时:判断分析树是否为空
a、空:直接将该新节点作为根节点
b、非空:查找出没有右节点的操作符节点,并将新节点作为操作符节点的右节点
B、当为操作符时:判断分析树中是否存在操作符节点(即左节点是否为空)
a、不存在:将根节点作为新节点的左节点,并将新节点设为根节点
b、存在:在当前分析树中自根节点依次查找其为操作符的最右节点,并判断新节点和已存在最右节点的操作符优先级:
1、新节点优先级大于:将当前最右节点的右节点(操作数)作为新节点的左节点,并将新节点替换成最右节点的右节点
2、新节点优先级等于:将当前的最右节点作为新节点的左节点,将新节点取代最右节点的位置,即将新节点作为最右节点的父节点的右节点
3、新节点优先级小于:自当前最右节点的父节点始查找优先级等于新节点的节点,然后以新找到的节点和新节点做替换,即按照2操作
总结:
重新对树有了认识,感觉挺不错的。还存在很多的不足之处:
1、没有对表达式的输入进行有效性控制
2、没有考虑括号等复杂的操作
3、没有进行报错控制
4、没有正确的显示树状,只是暂时的从网上搜了竖向的显示
附:源码下载