设为首页 加入收藏

TOP

编译器--支持变量和语句块的计算器(二)(一)
2015-07-20 17:29:04 来源: 作者: 【 】 浏览:6
Tags:编译器 支持 变量 语句 计算器

上篇文章记录了一个简单的计算器,但是只能计算一个表达式,比如计算8+3*5,得到值23.这次在其基础上添加了支持语句的功能,并且支持表达式中存在变量。比如下面:

num1 := 5;

num2 := num1+3*5;

num3 := num1 * (num2 - 20/5);


最后计算并返回的值是num3的值80.


根据这个例子,可以看出相比于上次那个简单的计算器,添加的特性包括1、支持赋值语句 2、支持变量 3、支持多条赋值语句,也就是语句块。其中语句之间使用分号分隔,赋值符号为”:=”,变量名的规则和c语言规则一致,以字母或者下划线开头,能包含数字、字母、下划线。


下面是新增的语法规则:

stmt -> id := expr;

stmts -> stmts stmt | stmt

id -> (letter|_) (letter | num)*


其中stmt代表一条语句,目前的语句只有一种,就是赋值语句,id代表一个标识符,在这里仅代表变量,id的定义使用正则表达式定义,letter代表字母。


另外一个值得关注的是新增了符号表,它是一个结构体数组,包含的结构体如下:

typedef struct sym

{

char *name; /*符号名字*/

int val; /*值*/

}Sym;

因为在这个简单的计算器中,所有变量只有一种值,即32位正数。所以Sym结构体的值val就是int。在我们的计算器中,变量可以不用声明而直接使用,如果事先没有赋值的话,那么变量的值将会是0。


符号表的填充是在生成了语法树之后,对其进行求值时进行的。比如单条语句num1 := 5; 在生成语法树

:=

/ \

num1 5

之后。开始调用calc函数对其递归求值。在对num1求值时,先检查符号表中是否存在符号num1,如果不存在则将其加入到符号表,并对它赋值为0。最后对赋值操作符“:=”求值时,将其右边表达式的值5赋给左边标识符num1,并且返回num1的值作为赋值操作符的返回值。

对于多条语句的求值,比如num1 := 5; num2 := num1 + 10; 生成的语法树如下:

:= ―> :=

/ \ / \

num1 5 num2 +

/ \

num1 10

可见对于两条语句分别生成了两棵语法树,其中第二棵语法树是作为第一棵语法树的兄弟节点存在的,下面是树节点的结构体TreeNode:

typedef struct treenode

{

struct treenode *child[CHILDNUM];/*子结点*/

struct treenode *brother;/*兄弟节点*/


NodeKind kind;/*节点类型:1. 语句 2.表达式*/

union

{

ExpK e;/*表达式子类型:常数、变量、数学表达式*/

StmtK s;/*语句类型:目前只有赋值语句一种*/

}attr;


union

{

int num;

TokenType tt;

char *name;

}val;/*属性对应的值*/

}TreeNode;


其中兄弟节点的存在就是为了将多条语句连接起来,以便在语法分析和求值的时候方便找到。


下面看下在代码中所做的相应改变,首先是在语法规则中新增的stmt和stmts规则所对应的两个函数:

TreeNode *stmt()
{
	TreeNode *node;
	TreeNode *lnode, *rnode;

	/*目前就只有赋值语句这一种,以一个ID类型值开头*/
	switch (token)
	{
		case ID:
			/*赋值左边的值为左节点*/
			lnode = newIdNode();
			match(ID);
			/*赋值号为根节点*/
			node = newNode();
			node->kind = Stmt;
			node->attr.s = AssignK;
			node->val.tt = ASSIGN;
			match(ASSIGN);
			/*赋值右边的表达式为右节点*/
			rnode = exp();

			node->child[0] = lnode;
			node->child[1] = rnode;
			/*匹配分号*/
			match(SEMI);
			break;
		default:
			printf("
  
   stmt: Unknown token.\n");
			exit(1);
			break;
	}

	return node;
}

  
由于目前语句只有赋值语句这一种,所以stmt函数就只需要解析语句就行了,赋值语句以一个ID类型的token开头,接着是赋值操作符,最后是一个exp表达式,解析表达式的函数exp()与上一篇文章中的一致。


下面是解析语句块的函数stmts()

TreeNode *stmts()
{
	TreeNode *node, *brother;
	TreeNode *head;
	
	head = node = stmt();

	while (ENDFILE != token)
	{
		brother = stmt();
		node->brother = brother;
		node = brother;
	}
	
	return head;
}
可以看到,stmts函数中先调用stmt解析一条语句,也就是说,源文件中至少得有一条语句。接着是一个while循环,不断调用stmt函数进行语句的解析,直到文件结束就退出while循环。此时返回一棵语法树,该树对应第一条语句,语法树的兄弟节点指向它后面的语句。


接下来需要关注的就是calc求值函数,

/*计算语法树的值*/
int calc(TreeNode *node)
{
	int val;
	int val1, val2;
	Sym *s;
	
	if (NULL == node)
	{
		printf("
  
   calc: syntax error.\n");
		exit(1);
	}

	/*根据节点的属性返回相应的值,目前节点有两种
	属性:数字或者操作符*/
	switch (node->kind)
	{
		case Exp:
			switch (node->attr.e)
		      	{
		      		/*数字属性节点直接返回值*/
		      		case ConstK:
		      			return node->val.num;
		      			break;
		      		/*操作符属性节点值需要先计算两个操作数的值,
		      			再根据操作符来计算最后的结果*/
		      		case OpK:
		      			val1 = calc(node->child[0]);
					if (NULL != node->child[1])
					{
		      				val2 = calc(node->child[1]);
					}
		      			switch (node->val.tt)
		      			{
		      				case ADD:
		      					val = val1 + val2;
		      					break;
		      				case MINUS:
		      					val = val1 - val2;
		      					break;
		      				case MUL:
		      					val = val1 * val2;
		      					break;
		      				case DIV:
		      					val =
首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇[ACM] ZOJ 3819 Average Score (.. 下一篇HDU 1828 Picture(矩形周长并)

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容:

·About - Redis (2025-12-26 08:20:56)
·Redis: A Comprehens (2025-12-26 08:20:53)
·Redis - The Real-ti (2025-12-26 08:20:50)
·Bash 脚本教程——Li (2025-12-26 07:53:35)
·实战篇!Linux shell (2025-12-26 07:53:32)