实验要求
【任务介绍】根据给定的上下文无关文法,分析任意一个算术表达式的语法结构。
【输入】任意的算术表达式。
【输出】与输入对应的一颗语法树或者错误。
【题目】设计一个程序,根据给定的上下文无关文法,构造一颗语法树来表达任意一个算术表达式的语法结构。要求:
1.基础文法:
<Expr> → <Term> <Expr1>
<Expr1> → <AddOp> <Term> <Expr1> | empty
<Term> → <Factor> <Term1>
<Term1> → <MulOp> <Factor> <Term1> | empty
<Factor> → id |number | ( <Expr> )
<AddOp> → + | -
<MulOp> → * | /
2.语法分析方法采用递归子程序法。
3.输入:形如x*1+2
的算术表达式,有+、-、*、/ 四种运算符,运算符的优先级、结合规则和括号的用法遵循惯例,有变量、整数两种运算对象。为简化问题,变量和整数均为只含有1个字符的单词,忽略空格等非必要的字符。
4.输出:输入正确时,输出其对应的语法树,树根标记为;输入错误时,输出error。
编译环境和语言
编程语言:C++
IDE:vs 2019
实验原理分析
对于给定文法定义:
<Expr> → <Term> <Expr1>
<Expr1> → <AddOp> <Term> <Expr1> | empty
<Term> → <Factor> <Term1>
<Term1> → <MulOp> <Factor> <Term1> | empty
<Factor> → id |number | ( <Expr> )
<AddOp> → + | -
<MulOp> → * | /
起始点为,因为采用的是LL(1)文法,所以需要不断左递归遍历,直到递归到终结字符,才会回溯,继续走另一条路,以此类推,直到最终回溯到。
对于LL(1)文法,即分析表不含多重定义入口的文法,换言之,直到不得不用右部表达式代替左部表达式的时候,再进行替换,形如:
<Expr> → <Expr><Term1> | <Expr><Term2>
就会导致不断的左递归而无法停止,这是因为在右部表达式的最左边,而我们又是使用的左递归,因此这就不是LL(1)文法。
程序关键部分分析
定义
string str = ""; //用来存储算术表达式
int location = 0; //用来定位算术表达式
bool flag = true; //用来判断该算术表达式是否合法
string tree_map[100]; //用来存储语法树
const int width = 3; //设置间隔为3
int draw_line(int row, int num);
void string_out(string s, int row, int column);
void word_out(char ch, int row, int column);
int tree_out(string s, int row, int loc);
void print_tree();
int Expr(int row, int column);
int Expr1(int row, int column);
int Term(int row, int column);
int Term1(int row, int column);
int Factor(int row, int column);
bool AddOp(char ch);
bool MulOp(char ch);
关键部分分析
首先是draw_line(int row, int num)函数,用来画横线隔开兄弟节点,长度为num:
int draw_line(int row, int num) { //用来画横线,隔开兄弟节点,返回下次开始的起始位置
int n = tree_map[row].size();
tree_map[row].append(num, '-');
return tree_map[row].size();
}
对于string_out(string s, int row, int column),用来将对应的数据存储到对应的位置上,首先需要判断输入的column是否与对应行数的长度相等,若不想等,则由于string数据类型的特性,必须将这中间部分填充上空格: