编译原理实验五:对算术表达式的递归下降分析

实验要求

【任务介绍】根据给定的上下文无关文法,分析任意一个算术表达式的语法结构。

【输入】任意的算术表达式。

【输出】与输入对应的一颗语法树或者错误。

【题目】设计一个程序,根据给定的上下文无关文法,构造一颗语法树来表达任意一个算术表达式的语法结构。要求:

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数据类型的特性,必须将这中间部分填充上空格:

void string_out(string s, int row, int column) {  //用来输出字符串
	if (tree_map[row].size() < column) {  //若不等,则说明中间需要填充空格
		int n = column - tree_map[row].size();
		tree_map[row].append(n, ' ');
	}
	tree_map[row].append(s);
}

对于word_out(char ch, int row, int column),用来存储对应的单个字符,其余与string_out(string s, int row, int column)类似:

void word_out(char ch, int row, int column) {  //用来输出单个字符
	if (tree_map[row].size() < column) {  //若不等,则说明中间需要填充空格
		int n = column - tree_map[row].size();
		tree_map[row].append(n, ' ');
	}
	tree_map[row] += ch;
}

对于tree_out(string s, int row, int loc),因为是画竖线,为了美观,这里需要根据父节点的长度找到中点,然后填充空格和竖线,至于返回值,是为了方便处理单个字符的位置:

/**画父子节点之间的竖线,s表示父亲节点的字符,loc表示父亲节点的起始位置
* 返回值用于处理单个字符的位置
*/
int tree_out(string s, int row, int loc) {
	int n1 = s.size() / 2;
	int n2 = loc + n1 - tree_map[row].size();
	tree_map[row].append(n2, ' ');
	tree_map[row] += '|';
	return n1 + loc;
}

对于print_tree(),将tree_map数组中的数据按行输出:

void print_tree() {
	for (int i = 0; i < 100; i++) {
		if (tree_map[i].size() != 0) {
			cout << tree_map[i] << endl;
		} else break;
	}
}

然后便是根据提供的文法定义声明的函数,都是首先存储该文法的名称以及对应的竖线,再继续递归下降,其中返回值用来处理每一行画横线的长度:

int Expr(int row, int column) {
	if (flag) {
		string_out("<Expr>", row, column);
		tree_out("<Expr>", ++row, column);
		int num1 = Term(++row, column);
		column = draw_line(row, num1 + width);
		int num2 = Expr1(row, column);
		return num1 + num2 + width + 6;
	}
}

int Expr1(int row, int column) {
	if (flag) {
		string_out("<Expr1>", row, column);
		tree_out("<Expr1>", ++row, column);
		if (AddOp(str[location])) {  //若第一个字符为+或-
			location++;
			string_out("<AddOp>", ++row, column);
			int loc = tree_out("<AddOp>", row + 1, column);
			word_out(str[location - 1], row + 2, loc);
			column = draw_line(row, width);
			int num1 = Term(row, column);
			column = draw_line(row, num1 + width);
			int num2 = Expr1(row, column);
			return num1 + num2 + width * 2 + 7;
		} else {  //否则输出为empty
			string_out("empty", ++row, column);
			return 7;
		}
	}
}

int Term(int row, int column) {
	if (flag) {
		string_out("<Term>", row, column);
		tree_out("<Term>", ++row, column);
		int num1 = Factor(++row, column);
		column = draw_line(row, num1 + width);
		int num2 = Term1(row, column);
		return num1 + num2 + width + 6;
	}
}

int Term1(int row, int column) {
	if (flag) {
		string_out("<Term1>", row, column);
		tree_out("<Term1>", ++row, column);
		if (MulOp(str[location])) {  //若第一个字符为*或/
			string_out("<MulOp>", ++row, column);
			int loc = tree_out("<MulOp>", row + 1, column);
			word_out(str[location], row + 2, loc);
			location++;
			column = draw_line(row, width);
			int num1 = Factor(row, column);
			column = draw_line(row, num1 + width);
			int num2 = Term1(row, column);
			return num1 + num2 + width * 2 + 7;
		} else {  //否则输出为empty
			string_out("empty", ++row, column);
			return 7;
		}
	}
}

int Factor(int row, int column) {
	if (flag) {
		string_out("<Factor>", row++, column);
		int loc = tree_out("<Factor>", row++, column);
		if (isalpha(str[location])) {  //若是字母则为变量
			word_out(str[location], row, loc);
			location++;
			return 8;
		}
		else if (isdigit(str[location])) {  //若为数字
			word_out(str[location], row, loc);
			location++;
			return 8;
		}
		else if (str[location] == '(') {  //若为(,则需要等)
			word_out(str[location], row, loc);
			location++;
			column = draw_line(row, width);
			int num1 = Expr(row, column);
			if (str[location] != ')') {  //若一直没有),则说明该算术表达式错误
				flag = false;  
				return 0;
			}
			else {
				int loc1 = draw_line(row, num1 + width);
				word_out(str[location], row, loc1);
				location++;
				return num1 + width * 2 + 8;
			}
		}
	}
}

bool AddOp(char ch) {
	if (ch == '+' || ch == '-')return true;
	return false;
}

bool MulOp(char ch) {
	if (ch == '*' || ch == '/')return true;
	return false;
}

最后便是main()函数,首先在输入的算术表达式最后加上一个#来表示结束标志,然后启动Expr(0, 0),其中(0, 0)即为string数组的起始位置,然后若最后一个字符为#,则说明完整遍历了整个算术表达式,便可以输出该语法树,否则直接输出Error:

int main() {
	cout << "请输入一个算术表达式:";
	cin >> str;
	str.resize(str.size() + 1);
	str[str.size() - 1] = '#';  //将#号设置为结束标志
	Expr(0, 0);
	if (str[location] == '#') {
		cout << "Correct!" << endl;
		cout << "接下来输出该算法表达式的语法树:" << endl;
		print_tree();
	}
	else cout << "Error!" << endl;
	return 0;
}

程序测试

输入数据:

1+2*(3-1)

运行结果如下:

在这里插入图片描述

总结

对于构造语法树,我想到的方法是用一个string数组来存储每一行的数据,因为是递归下降,所以我的相关文法函数都是有返回值的,用来记录每一行画横线的长度,然后在不同行数记录对应的数据。

以起始点Expr文法为例:

int Expr(int row, int column) {
	if (flag) {
		string_out("<Expr>", row, column);
		tree_out("<Expr>", ++row, column);
		int num1 = Term(++row, column);
		column = draw_line(row, num1 + width);
		int num2 = Expr1(row, column);
		return num1 + num2 + width + 6;
	}
}

返回值中,num1是从Term文法得到的长度,num2是从Expr1文法得到的长度,width为Term和Expr1之间的间隔,然后6为的字符长度,如此便能够确定每次输出横线的个数以保证不会出现覆盖,但是也正因此,我的语法树的列数会非常庞大,从上面的测试用例也可以发现,仅仅只是一个很简单的算术表达式,输出的语法树的列数都已经很庞大了。

完整代码

#include<iostream>
#include<cctype>
using namespace std;

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);

int draw_line(int row, int num) {  //用来画横线,隔开兄弟节点,返回下次开始的起始位置
	int n = tree_map[row].size();
	tree_map[row].append(num, '-');
	return tree_map[row].size();
}

void string_out(string s, int row, int column) {  //用来输出字符串
	if (tree_map[row].size() < column) {  //若不等,则说明中间需要填充空格
		int n = column - tree_map[row].size();
		tree_map[row].append(n, ' ');
	}
	tree_map[row].append(s);
}

void word_out(char ch, int row, int column) {  //用来输出单个字符
	if (tree_map[row].size() < column) {  //若不等,则说明中间需要填充空格
		int n = column - tree_map[row].size();
		tree_map[row].append(n, ' ');
	}
	tree_map[row] += ch;
}

/**画父子节点之间的竖线,s表示父亲节点的字符,loc表示父亲节点的起始位置
* 返回值用于处理单个字符的位置
*/
int tree_out(string s, int row, int loc) {
	int n1 = s.size() / 2;
	int n2 = loc + n1 - tree_map[row].size();
	tree_map[row].append(n2, ' ');
	tree_map[row] += '|';
	return n1 + loc;
}

void print_tree() {
	for (int i = 0; i < 100; i++) {
		if (tree_map[i].size() != 0) {
			cout << tree_map[i] << endl;
		} else break;
	}
}

int Expr(int row, int column) {
	if (flag) {
		string_out("<Expr>", row, column);
		tree_out("<Expr>", ++row, column);
		int num1 = Term(++row, column);
		column = draw_line(row, num1 + width);
		int num2 = Expr1(row, column);
		return num1 + num2 + width + 6;
	}
}

int Expr1(int row, int column) {
	if (flag) {
		string_out("<Expr1>", row, column);
		tree_out("<Expr1>", ++row, column);
		if (AddOp(str[location])) {  //若第一个字符为+或-
			location++;
			string_out("<AddOp>", ++row, column);
			int loc = tree_out("<AddOp>", row + 1, column);
			word_out(str[location - 1], row + 2, loc);
			column = draw_line(row, width);
			int num1 = Term(row, column);
			column = draw_line(row, num1 + width);
			int num2 = Expr1(row, column);
			return num1 + num2 + width * 2 + 7;
		} else {  //否则输出为empty
			string_out("empty", ++row, column);
			return 7;
		}
	}
}

int Term(int row, int column) {
	if (flag) {
		string_out("<Term>", row, column);
		tree_out("<Term>", ++row, column);
		int num1 = Factor(++row, column);
		column = draw_line(row, num1 + width);
		int num2 = Term1(row, column);
		return num1 + num2 + width + 6;
	}
}

int Term1(int row, int column) {
	if (flag) {
		string_out("<Term1>", row, column);
		tree_out("<Term1>", ++row, column);
		if (MulOp(str[location])) {  //若第一个字符为*或/
			string_out("<MulOp>", ++row, column);
			int loc = tree_out("<MulOp>", row + 1, column);
			word_out(str[location], row + 2, loc);
			location++;
			column = draw_line(row, width);
			int num1 = Factor(row, column);
			column = draw_line(row, num1 + width);
			int num2 = Term1(row, column);
			return num1 + num2 + width * 2 + 7;
		} else {  //否则输出为empty
			string_out("empty", ++row, column);
			return 7;
		}
	}
}

int Factor(int row, int column) {
	if (flag) {
		string_out("<Factor>", row++, column);
		int loc = tree_out("<Factor>", row++, column);
		if (isalpha(str[location])) {  //若是字母则为变量
			word_out(str[location], row, loc);
			location++;
			return 8;
		}
		else if (isdigit(str[location])) {  //若为数字
			word_out(str[location], row, loc);
			location++;
			return 8;
		}
		else if (str[location] == '(') {  //若为(,则需要等)
			word_out(str[location], row, loc);
			location++;
			column = draw_line(row, width);
			int num1 = Expr(row, column);
			if (str[location] != ')') {  //若一直没有),则说明该算术表达式错误
				flag = false;  
				return 0;
			}
			else {
				int loc1 = draw_line(row, num1 + width);
				word_out(str[location], row, loc1);
				location++;
				return num1 + width * 2 + 8;
			}
		}
	}
}

bool AddOp(char ch) {
	if (ch == '+' || ch == '-')return true;
	return false;
}

bool MulOp(char ch) {
	if (ch == '*' || ch == '/')return true;
	return false;
}

int main() {
	cout << "请输入一个算术表达式:";
	cin >> str;
	str.resize(str.size() + 1);
	str[str.size() - 1] = '#';  //将#号设置为结束标志
	Expr(0, 0);
	if (str[location] == '#') {
		cout << "Correct!" << endl;
		cout << "接下来输出该算法表达式的语法树:" << endl;
		print_tree();
	}
	else cout << "Error!" << endl;
	return 0;
}
  • 10
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
附录c 编译程序实验 实验目的:用c语言对一个简单语言的子集编制一个一遍扫描的编译程序,以加深对编译原理的理解,掌握编译程序的实现方法和技术。 语法分析 C2.1 实验目的 编制一个递归下降分析程序,实现对词法分析程序所提供的单词序列的语法检查和结构分析. C2.2 实验要求 利用C语言编制递归下降分析程序,并对简单语言进行语法分析. C2.2.1待分析的简单语言的语法 实验目的 通过上机实习,加深对语法制导翻译原理的理解,掌握将语法分析所识别的语法成分变换为中间代码的语义翻译方法. 实验要求 采用递归下降语法制导翻译法,对算术表达式、赋值语句进行语义分析并生成四元式序列。 实验的输入和输出 输入是语法分析提供的正确的单词串,输出为三地址指令形式的四元式序列。 例如:对于语句串 begin a:=2+3*4;x:=(a+b)/c end# 输出的三地址指令如下: (1) t1=3*4 (2) t2=2+t1 (3) a=t2 (4) t3=a+b (5) t4=t3/c (6) x=t4 算法思想 1设置语义过程 (1) emit(char *result,char *arg1,char *op,char *ag2) 该函数功能是生成一个三地址语句送到四元式表中。 四元式表的结构如下: struct {char result[8]; char ag1[8]; char op[8]; char ag2[8]; }quad[20]; (2)char *newtemp() 该函数回送一个新的临时变量名,临时变量名产生的顺序为T1,T2,…. Char *newtemp(void) { char *p; char m[8]; p=(char *)malloc(8); k++; itoa(k,m,10); strcpy(p+1,m); p[0]=’t’; return(p); } (2)主程序示意图如图c.10所示。 (2) 函数lrparser在原来语法分析的基础上插入相应的语义动作:将输入串翻译成四元式序列。在实验中我们只对表达式、赋值语句进行翻译。 语义分析程序的C语言程序框架 int lrparser() { int schain=0; kk=0; if(syn=1) { 读下一个单词符号; schain=yucu; /调用语句串分析函数进行分析/ if(syn=6) { 读下一个单词符号; if(syn=0 && (kk==0)) 输出(“success”); } else { if(kk!=1 ) 输出 ‘缺end’ 错误;kk=1;} else{输出’begin’错误;kk=1;} } return(schain); int yucu() { int schain=0; schain=statement();/调用语句分析函数进行分析/ while(syn=26) {读下一个单词符号; schain=statement(); /调用语句分析函数进行分析/ } return(schain); } int statement() { char tt[8],eplace[8]; int schain=0; {switch(syn) {case 10: strcpy(tt,token); scanner(); if(syn=18) {读下一个单词符号; strcpy(eplace,expression()); emit(tt,eplace,””,””); schain=0; } else {输出’缺少赋值号’的错误;kk=1; } return(schain); break; } } char *expression(void) {char *tp,*ep2,*eplace,*tt; tp=(char *)malloc(12);/分配空间/ ep2=(char *)malloc(12); eplace=(char *)malloc(12); tt =(char )malloc(12); strcpy(eplace,term ());/调用term分析产生表达式计算的第一项eplace/ while(syn=13 or 14) { 操作符 tt= ‘+’或者‘—’; 读下一个单词符号; strcpy(ep2,term());/调用term分析产生表达式计算的第二项ep2/ strcpy(tp,newtemp());/调用newtemp产生临时变量tp存储计算结果/ emit(tp,eplace,tt,ep2);/生成四元式送入四元式表/ strcpy(eplace,tp); } return(eplace); } char *term(void)/仿照函数expression编写/ char *factor(void) {char *fplace; fplace=(char *)malloc(12); strcpy(fplace, “ ”); if(syn=10) {strcpy(fplace,,token);/将标识符token的值赋给fplace/ 读下一个单词符号; } else if(syn=11) {itoa(sum,fplace,10); 读下一个单词符号; } else if (syn=27) {读下一个单词符号; fplace=expression();/调用expression分析返回表达式的值/ if(syn=28) 读下一个单词符号; else{输出‘}’错误;kk=1; } } else{输出‘(’错误;kk=1; } return(fplace); }
语法分析编译原理中的重要环节,它的主要任务是将词法分析器输出的词法单元序列转换为语法分析树或语法分析图,以便于后续的语义分析和代码生成。语法分析器的实现方式有多种,其中最常用的是基于文法的自上而下的递归下降分析法和基于文法的自下而上的移进-归约分析法。 在本实验中,我们将使用C++语言实现一个简单的递归下降分析法的语法分析器,实现对类C语言的一个子集进行语法分析。该子集包含了整型变量声明、赋值语句、算术表达式、条件语句和循环语句等基本语法结构。 1. 文法定义 我们定义的子集语法规则如下: ``` <program> ::= <stmt_list> <stmt_list> ::= <stmt> | <stmt> <stmt_list> <stmt> ::= <decl_stmt> | <assign_stmt> | <if_stmt> | <while_stmt> <decl_stmt> ::= int <id>; <assign_stmt> ::= <id> = <expr>; <if_stmt> ::= if (<condition>) <stmt> <while_stmt> ::= while (<condition>) <stmt> <condition> ::= <expr> <rel_op> <expr> <expr> ::= <term> | <term> <add_op> <expr> <term> ::= <factor> | <factor> <mult_op> <term> <factor> ::= <int> | <id> | (<expr>) <id> ::= <letter> | <id> <letter> | <id> <digit> <int> ::= <digit> | <int> <digit> <letter> ::= a | b | ... | z | A | B | ... | Z <digit> ::= 0 | 1 | ... | 9 <add_op> ::= + | - <mult_op> ::= * | / <rel_op> ::= < | > | <= | >= | == | != ``` 其中,<program>是整个程序的入口,<stmt_list>表示语句列表,<stmt>表示语句,<decl_stmt>表示变量声明语句,<assign_stmt>表示赋值语句,<if_stmt>表示条件语句,<while_stmt>表示循环语句,<condition>表示条件表达式,<expr>表示算术表达式,<term>表示项,<factor>表示因子,<id>表示标识符,<int>表示整数常量,<letter>表示字母,<digit>表示数字,<add_op>表示加减运算符,<mult_op>表示乘除运算符,<rel_op>表示关系运算符。 2. 代码实现 在实现递归下降分析法的语法分析器时,我们需要实现对以上语法规则的递归下降分析函数,每个函数对应一个语法规则。 首先,我们需要定义一个词法分析器,用于将源代码转换为词法单元序列。在本实验中,我们将使用一个简单的词法分析器,它可以处理int关键字、标识符、整数常量、加减乘除运算符、关系运算符、左右括号和分号等词法单元。 ```c++ #include <iostream> #include <string> #include <vector> #include <stdexcept> using namespace std; // 定义词法单元类型 enum TokenKind { TK_INT, // int关键字 TK_ID, // 标识符 TK_NUM, // 整数常量 TK_PLUS, // + TK_MINUS, // - TK_MUL, // * TK_DIV, // / TK_LT, // < TK_GT, // > TK_LE, // <= TK_GE, // >= TK_EQ, // == TK_NE, // != TK_LPAREN, // ( TK_RPAREN, // ) TK_SEMICOLON // ; }; // 定义词法单元结构体 struct Token { TokenKind kind; // 词法单元类型 string str; // 词法单元字符串 }; // 定义词法分析器 class Lexer { public: Lexer(const string& source) : src(source), pos(0) {} // 获取下一个词法单元 Token getNextToken() { // 跳过空白字符 while (isspace(src[pos])) pos++; // 处理数字 if (isdigit(src[pos])) { string numStr; while (isdigit(src[pos])) { numStr += src[pos++]; } return { TK_NUM, numStr }; } // 处理标识符 if (isalpha(src[pos])) { string idStr; while (isalnum(src[pos])) { idStr += src[pos++]; } return { TK_ID, idStr }; } // 处理运算符和括号 switch (src[pos]) { case '+': pos++; return { TK_PLUS, "+" }; case '-': pos++; return { TK_MINUS, "-" }; case '*': pos++; return { TK_MUL, "*" }; case '/': pos++; return { TK_DIV, "/" }; case '<': if (src[pos + 1] == '=') { pos += 2; return { TK_LE, "<=" }; } else { pos++; return { TK_LT, "<" }; } case '>': if (src[pos + 1] == '=') { pos += 2; return { TK_GE, ">=" }; } else { pos++; return { TK_GT, ">" }; } case '=': if (src[pos + 1] == '=') { pos += 2; return { TK_EQ, "==" }; } else { throw runtime_error("invalid token"); } case '!': if (src[pos + 1] == '=') { pos += 2; return { TK_NE, "!=" }; } else { throw runtime_error("invalid token"); } case '(': pos++; return { TK_LPAREN, "(" }; case ')': pos++; return { TK_RPAREN, ")" }; case ';': pos++; return { TK_SEMICOLON, ";" }; default: throw runtime_error("invalid token"); } } private: string src; // 源代码 size_t pos; // 当前位置 }; ``` 接下来,我们依次实现递归下降分析函数。函数的实现方式为:首先获取当前词法单元,然后根据语法规则进行分析,如果符合规则则继续获取下一个词法单元,否则抛出异常。 ```c++ class Parser { public: Parser(const string& source) : lexer(source) {} // 解析程序 void parseProgram() { parseStmtList(); } private: // 解析语句列表 void parseStmtList() { parseStmt(); Token token = lexer.getNextToken(); if (token.kind != TK_SEMICOLON) { throw runtime_error("missing semicolon"); } if (token.kind != TK_EOF) { parseStmtList(); } } // 解析语句 void parseStmt() { Token token = lexer.getNextToken(); switch (token.kind) { case TK_INT: parseDeclStmt(); break; case TK_ID: parseAssignStmt(); break; case TK_IF: parseIfStmt(); break; case TK_WHILE: parseWhileStmt(); break; default: throw runtime_error("invalid statement"); } } // 解析变量声明语句 void parseDeclStmt() { Token token = lexer.getNextToken(); if (token.kind != TK_ID) { throw runtime_error("missing identifier"); } token = lexer.getNextToken(); if (token.kind != TK_SEMICOLON) { throw runtime_error("missing semicolon"); } } // 解析赋值语句 void parseAssignStmt() { Token token = lexer.getNextToken(); if (token.kind != TK_ASSIGN) { throw runtime_error("missing assignment operator"); } parseExpr(); token = lexer.getNextToken(); if (token.kind != TK_SEMICOLON) { throw runtime_error("missing semicolon"); } } // 解析条件语句 void parseIfStmt() { Token token = lexer.getNextToken(); if (token.kind != TK_LPAREN) { throw runtime_error("missing left parenthesis"); } parseCondition(); token = lexer.getNextToken(); if (token.kind != TK_RPAREN) { throw runtime_error("missing right parenthesis"); } parseStmt(); } // 解析循环语句 void parseWhileStmt() { Token token = lexer.getNextToken(); if (token.kind != TK_LPAREN) { throw runtime_error("missing left parenthesis"); } parseCondition(); token = lexer.getNextToken(); if (token.kind != TK_RPAREN) { throw runtime_error("missing right parenthesis"); } parseStmt(); } // 解析条件表达式 void parseCondition() { parseExpr(); Token token = lexer.getNextToken(); switch (token.kind) { case TK_LT: case TK_GT: case TK_LE: case TK_GE: case TK_EQ: case TK_NE: parseExpr(); break; default: throw runtime_error("missing relational operator"); } } // 解析算术表达式 void parseExpr() { parseTerm(); Token token = lexer.getNextToken(); while (token.kind == TK_PLUS || token.kind == TK_MINUS) { parseTerm(); token = lexer.getNextToken(); } lexer.ungetToken(); } // 解析项 void parseTerm() { parseFactor(); Token token = lexer.getNextToken(); while (token.kind == TK_MUL || token.kind == TK_DIV) { parseFactor(); token = lexer.getNextToken(); } lexer.ungetToken(); } // 解析因子 void parseFactor() { Token token = lexer.getNextToken(); switch (token.kind) { case TK_NUM: case TK_ID: break; case TK_LPAREN: parseExpr(); token = lexer.getNextToken(); if (token.kind != TK_RPAREN) { throw runtime_error("missing right parenthesis"); } break; default: throw runtime_error("invalid factor"); } } private: Lexer lexer; // 词法分析器 }; ``` 3. 测试样例 我们编写以下测试样例,用于测试语法分析器的正确性。 ```c++ int main() { string source = "int a; a = 1 + 2 * 3; if (a < 10) { a = a + 1; } while (a < 20) { a = a + 2; }"; Parser parser(source); parser.parseProgram(); return 0; } ``` 以上测试样例包含了变量声明、赋值语句、算术表达式、条件语句和循环语句等基本语法结构,用于测试语法分析器的正确性。 4. 总结 本实验通过实现一个简单的递归下降分析法的语法分析器,实现了对类C语言的一个子集进行语法分析递归下降分析法是编译原理中最常用的语法分析方法之一,它的实现方式简单直观,易于理解和实现,但是它存在递归调用深度过大等问题,需要注意优化和调试。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

花无凋零之时

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值