⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 parser.y

📁 《编译方法》课程设计内容2.《编译方法》课程设计内容
💻 Y
📖 第 1 页 / 共 2 页
字号:
   地址到函数调用语句的四元式代码中. 

   为了简化编译器, 我们没有在整个分析结束后对函数调用语句是否还有没有
   回填, 可以考虑在语法分析结束时检查符号表是否存在这样的符号, 即符号的类型
   是未定义, 而LABEL2非空

   "FUNC fname(args) stmt" 翻译为 "LABELn; BEGINFUNC; args; stmt; 
   ENDFUNC"  */

{
	TAC *tlist ;			 /* The backpatch list */

	TAC *tlab ;			 /* Label at start of function */
	TAC *tbegin ;			 /* BEGINFUNC marker */
	TAC *tend ;			 /* ENDFUNC marker */

	/* 查看函数名的类型,如果不是未定义, 则报错返回, 否则产生函数体的
	   首尾标号语句,对标号语句,形参列表语句和函数体语句的代码进行链接.
	   最后对调用该函数语句的四元式代码进行回填 */

	if( func->type != T_UNDEF )
	{
		error( "function already declared" ) ;
		return NULL ;
	}

	tlab   = mktac( TAC_LABEL,     mklabel( next_label++ ), NULL, NULL ) ;
	tbegin = mktac( TAC_BEGINFUNC, NULL, NULL, NULL ) ;
	tend   = mktac( TAC_ENDFUNC,   NULL, NULL, NULL ) ;

	tbegin->prev = tlab ;
	code         = join_tac( args, code ) ;
	tend->prev   = join_tac( tbegin, code ) ;

	tlist = func->LABEL2 ;			 /* 回填链表的链首 */

	while( tlist != NULL )
	{
		TAC *tnext = tlist->LB ;  	 /* 下一项 */

		tlist->LB  = tlab ;
		tlist      = tnext ;
	}

	func->type   = T_FUNC ;		 /* 定义符号表的各项 */
	func->LABEL2 = tlab ;		 /* LABEL2指向函数的入口地址 */

	return tend ;

}	/* TAC *do_func( SYMB *func,
		     	 TAC  *args,
			 TAC  *code ) */


TAC *declare_var( SYMB *var )

/*  由于变量共享唯一的符号表, 因此任何地方出现的变量不能重名,
    变量声明语句将翻译为: 完成对符号表单元各项的填写,其中offset设为-1,
    表示没有分配实际的内存空间,这部分工作将在代码生成时完成; 产生变量
    声明四元式代码, 该代码的作用是在代码生成时, 对变量分配存储空间,
    注意, 在此我们忽略了变量作用域的定义, 这对一个实用编译器是必不可少的 */


{
	if( var->type != T_UNDEF )
	{
		error( "variable already declared" ) ;
		return NULL ;
	}

	var->type  = T_VAR ;
	var->ADDR2 = -1 ;		 /* 设为无效*/

	/* 生成变量定义四元式代码 */

	return  mktac( TAC_VAR, var, NULL, NULL ) ;

}	/* TAC *declare_var( SYMB *var ) */


TAC *do_assign( SYMB  *var,	 /* 被赋值的变量 */
		ENODE *expr )	 /* 表达式 */

/* 赋值语句的翻译, 首先检查表达式左边的变量是否定义, 如果没有, 系统报错;
   否则, 生成赋值四元式中间代码, 释放表达式结点的内存单元.

   "var := expr"将翻译为: "expr语句; COPY, var, expr" */
 

{
	TAC  *code ;

	/* Warn if variable not declared, then build code */

	if( var->type != T_VAR )
		error( "assignment to non-variable" ) ;

	code       = mktac( TAC_COPY, var, expr->res, NULL ) ;
	code->prev = expr->tac ;
	free_enode( expr ) ;		 /* 表达式结点信息不再被使用 */

	return code ;

}	/* TAC *do_assign( SYMB  *var,
			   ENODE *expr ) */


ENODE *do_bin(  int    binop,		 /* 二元运算符 */
		ENODE *expr1,		 /* 运算量 */
		ENODE *expr2 )

/* 二元表达式的翻译, 由于表达式必须翻译为两层含义: 表达式的四元式代码和
   表达式中间结果, 因此我们用树结构记录表达式的翻译信息. 运算量expr1和
   expr2一旦参与新的运算后(如: expr -> expr1 binop expr2), 其作为树结点
   的辅助信息不再被使用(其四元式代码链将链接到运算结果expr结点上; 而表达
   式的值将被expr的四元式代码引用), 因此在翻译后, expr1和expr2的结点信息
   不再被使用, 我们可以释放其内存单元, 由于expr需要一个心得结点, 所以我们
   可以保留expr1的结点给expr使用. 在翻译常数表达式时, 我们可以做一点优化,
   即, 在编译阶段就计算出其运算结果, */

{
	TAC  *temp ;			 /* 临时四元式 */
	TAC  *res ;			 /* 结果四元式 */

	/* 处理expr1和expr2都是常数的情况, */

	if(( expr1->ETYPE == T_INT ) && ( expr2->ETYPE == T_INT ))
	{
		switch( binop )		 /* 选择运算符号 */
		{
		case TAC_ADD:

			expr1->EVAL1 = expr1->EVAL1 + expr2->EVAL1 ;
			break ;

		case TAC_SUB:

			expr1->EVAL1 = expr1->EVAL1 - expr2->EVAL1 ;
			break ;

		case TAC_MUL:

			expr1->EVAL1 = expr1->EVAL1 * expr2->EVAL1 ;
			break ;

		case TAC_DIV:

			expr1->EVAL1 = expr1->EVAL1 / expr2->EVAL1 ;
			break ;

		}

		free_symb( expr2->res ) ;  /* 释放expr2的内存 */
		free_enode( expr2 ) ;

		return expr1 ;		   /* 返回新的结点 */
	}

	/* 处理不是常数的情况 */

	temp       = mktac( TAC_VAR, mktmp(), NULL, NULL ) ;
	temp->prev = join_tac( expr1->tac, expr2->tac ) ;
	res        = mktac( binop, temp->VA, expr1->res, expr2->res ) ;
	res->prev  = temp ;

	expr1->res = temp->VA ;
	expr1->tac = res ;
	free_enode( expr2 ) ;

	return expr1 ;	

}	/* ENODE *do_bin(  int    binop,
	 		   ENODE *expr1,
			   ENODE *expr2 ) */


ENODE *do_un(  int    unop,		 /* 一元运算 */
	       ENODE *expr )		 /* 运算量 */

/* 完全和二元运算的翻译一样 */


{
	TAC  *temp ;			 /* 临时四元式 */
	TAC  *res ;			 /* 结果四元式 */

	/* 处理常数情况 */

	if( expr->ETYPE == T_INT )
	{
		switch( unop )		 /* 选择运算符, VSL只有一元减 */
		{
		case TAC_NEG:

			expr->EVAL1 = - expr->EVAL1 ;
			break ;
		}

		return expr ;		   /* 返回结果表达式 */
	}

	/* 处理非常数情况 */

	temp       = mktac( TAC_VAR, mktmp(), NULL, NULL ) ;
	temp->prev = expr->tac ;
	res        = mktac( unop, temp->VA, NULL, expr->res ) ;
	res->prev  = temp ;

	expr->res = temp->VA ;
	expr->tac = res ;

	return expr ;	

}	/* ENODE *do_un(  int    unop,
	 		  ENODE *expr ) */


ENODE *do_fnap( SYMB  *func,		 /* 函数调用 */
		ENODE *arglist )	 /* 实参列表 */

/* 函数调用的翻译: 首先类型检查判断函数名是否有定义, 如果函数名的类型不是
   T_FUNC或T_UNDEF, 表示该标识符不是函数, 分析出错返回; 否则, 链结arglist
   中的每个实参代码, 形成arglist代码链, 紧接着对每个实参产生TAC_ARG三地址
   码(注意TAC_ARG必须是连续的代码, 最后产生TAC_CALL代码. 如果函数的类型是
   T_UNDEF, 表示该函数调用是先调用后引用, 此时, 需要将TAC_CALL语句加入到函
   数名的回填链中.

   例如 "fname(expr1, expr2)" 将翻译为 "expr1语句; expr2语句; ARG, var1;
   ARG, var2; CALL, result, fname", 其中, varn是临时变量, 保存exprn的计
   算结果; res保存函数的返回值 */

{
	ENODE  *alt ;			 /* 用于在实参链上移动 */
	SYMB   *res ;			 /* 保存函数调用的返回结果 */
	TAC    *code ;			 /* 保存最后的代码链 */
	TAC    *temp ;			 /* 临时变量 */

	/* 类型检查 */

	if(( func->type != T_UNDEF ) && ( func->type != T_FUNC ))
	{
		error( "function declared other than function" );
		return NULL ;
	}

	res   = mktmp() ;		   /* 函数调用表达式的结果 */
	code  = mktac( TAC_VAR, res, NULL, NULL ) ;

	for( alt = arglist ; alt != NULL ; alt = alt->next )  
			/* 链结实参代码 */
		code = join_tac( code, alt->tac ) ;

	while( arglist != NULL )	 /* 产生连续的TAC_ARG语句 */
	{
		temp       = mktac( TAC_ARG, arglist->res, NULL, NULL ) ;
		temp->prev = code ;
		code       = temp ;

		alt = arglist->next ;
		free_enode( arglist ) ;	 /* 释放内存 */
		arglist = alt ;
	} ;

		/* 产生函数调用语句 */
	temp       = mktac( TAC_CALL, res, (SYMB *)func->LABEL2, NULL ) ;
		/* 注意LABEL2有两种可能, 一种是函数代码的入口标号, 
		   另一种是回填链表的表头, 而回填链表的每个元素的类型
		   是TAC, 并且是通过LABEL2进行链结, 两种情况都可以这样
                   处理, 对第二种, 只要将函数的表头设置为temp即可; 另一个
     		   要注意的是由于TAC数据结构的运算量类型是SYMB *, 而LABEL2
            	   的类型是TAC *, 因此要用强制类型转换, 将之转换为SYMB指针,
		   在代码生成时, 再将TAC_CALL的第二运算量转换回来即可, 下面
		   的翻译还有类似的情况, 不再作特别的注释. 指针的类型转换
		   是C语言处理的一个常用手段, 使用转换后的指针一定要再转换
 		   回来 */
 
	temp->prev = code ;
	code       = temp ;

	/* 如果函数的类型是T_UNDEF, 则设置回填链的新表头为temp */

	if( func->type == T_UNDEF )
		func->LABEL2 = code ;

	return mkenode( NULL, res, code ) ;

}	/* ENODE *do_fnap( SYMB  *func,
		           ENODE *arglist ) */


TAC *do_lib( int   rtn,			 /* 库函数码 */
	     SYMB *arg )		 /* 实参 */

/* VSL语言采用简单的方法处理库函数的调用, 即将两个库函数的标号语句
   三地址码预先装入数组library中, 并且预留相应的标号(2和4), 库函数
   PRINTN和PRINTS的形参只有1个, 而VSL的打印语句的参数个数是可变的,
   在处理这样的变参情况, VSL采用的策略是在形式文法这一级上进行处理,
   即, 通过PRINT关键字, 引入变参调用. 这样, 当词法分析器识别"PRINT  
   item1, item2", 我们可以将之翻译为两个连续的打印函数的调用语句:
   "PRINT item1 PRINT item2", 这样就避免了在中间代码一级上处理变参的
   情况. 这样的解决方案仅是一个权宜之计, 它不能处理用户自定义的边
   参函数. 对于每个"PRINT item", 将之翻译为: TAC_ARG, item和TAC_CALL,
   library[rtn]即可, 而rtn可以在语法这一级上确定item是TEXT或是expression,
   即是字符串常量或是整数 */

{
	TAC *a = mktac( TAC_ARG, arg, NULL, NULL ) ;
	TAC *c = mktac( TAC_CALL, NULL, (SYMB *)library[rtn], NULL ) ;

	c->prev = a ;

	return c ;

}	/* TAC *do_lib( int   rtn,
	   		SYMB *arg ) */


TAC *do_if( ENODE *expr,		 /* 条件 */
	    TAC   *stmt )		 /* 执行的语句 */

/* 条件语句的翻译: 为了方便将if结构分为两个情况处理, 一个是do_if(), 
   它没有else挂靠; 另一个是do_test(), 它有else挂靠, 在翻译if结构时
   我们没有采用通常的回填技术,  而是使用家标号的方法, 这样: "IF expr
   THEN stmt FI"翻译为: "expr语句; IFZ, LABELn expr; stmt; LABELn"; 
   
   由此可能产生标号的重叠使用, 如: "IF expr1 THEN IF expr2 THEN stmt
    FI FI"的三地址码是: "expr语句; IFZ, LABELn expr; IFZ, LABELm, expr2;
   LABELm; LABELn", 其中LABELm和LABELn连续出现, 

   由于在最后的目标代码中标号作为伪指令还保留, 因此该问题只有到最后
   VAM二进制文件生成时进行处理, 处理的方法是对编译器生成的汇编代码
   进行二次扫描, 第一次扫时对非标号语句进行计数,而每个标号语句将转化
   为该标号语句之后的第一条非标号语句对应的计数,因此, 这样连续的标号
   语句的问题最终可以解决, 由于在目标语言的汇编码中加入了伪指令标号, 
   这使得中间语言的翻译简化, 但对此留下的问题, 我们必须用两遍(two pass)
   的方式解决, 为此处的简化付出代价, 而对一般的编译器都是使用回填技术
   进行处理, 以避免two pass处理 */

{
	TAC *label = mktac( TAC_LABEL, mklabel( next_label++ ), NULL, NULL ) ;
	TAC *code  = mktac( TAC_IFZ, (SYMB *)label, expr->res, NULL ) ;

	code->prev  = expr->tac ;
	code        = join_tac( code, stmt ) ;
	label->prev = code ;

	free_enode( expr ) ;		 /* 表达式不再使用 */

	return label ;
}	/* TAC *do_if( ENODE *expr,
	    	       TAC   *stmt ) */


TAC *do_test( ENODE *expr,		 /* 条件 */
	      TAC   *stmt1,		 /* THEN部分 */
	      TAC   *stmt2 )		 /* ELSE部分 */

/* 有else挂靠: "IF expr THEN stmt1 ELSE stmt2 FI"将翻译为: 
   "expr语句; IFZ, label1, expr;  stmt1; goto, LABEL2; LABEL1; 
    stmt2; LABEL2" */

{
	TAC *label1 = mktac( TAC_LABEL, mklabel( next_label++ ), NULL, NULL ) ;
	TAC *label2 = mktac( TAC_LABEL, mklabel( next_label++ ), NULL, NULL ) ;
	TAC *code1  = mktac( TAC_IFZ, (SYMB *)label1, expr->res, NULL ) ;
	TAC *code2  = mktac( TAC_GOTO, (SYMB *)label2, NULL, NULL ) ;

	code1->prev  = expr->tac ;		   /* 按上次序连接代码 */
	code1        = join_tac( code1, stmt1 ) ;
	code2->prev  = code1 ;
	label1->prev = code2 ;
	label1       = join_tac( label1, stmt2 ) ;
	label2->prev = label1 ;

	free_enode( expr ) ;		 /* 释放expr内存 */

	return label2 ;

}	/* TAC *do_test( ENODE *expr,
	      		 TAC   *stmt1,
	      		 TAC   *stmt2 ) */


TAC *do_while( ENODE *expr,		 /* 循环条件 */
	       TAC   *stmt )		 /* 循环体 */

/* 循环语句的翻译: 处理的方法和条件语句一样通过标号语句来引导分支的
   转移. "WHILE expr DO stmt DONE" 翻译为: "LABELm; expr语句;  
   IFZ, LABELn expr; stmt; goto LABELm; LABELn", 如果将其中的 "stmt; 
   goto LABELm" 看成是stmt1; 则: "expr语句;  IFZ, LABELn expr; stmt1;
   LABELn" 是一个无挂靠的IF结构, 因此可以使用函数do_if() */   

{
	TAC *label = mktac( TAC_LABEL, mklabel( next_label++ ), NULL, NULL ) ;
	TAC *code  = mktac( TAC_GOTO, (SYMB *)label, NULL, NULL ) ;

	code->prev = stmt ;		 /* 产生stmt1 */

	return join_tac( label, do_if( expr, code )) ;

}	/* TAC *do_while( ENODE *expr,
	       		  TAC   *stmt ) */


ENODE *mkenode( ENODE *next,
		SYMB  *res,
		TAC   *code )

/* 产生表达式结点, 本函数本应该作为编译的辅助函数在main.c模块中
   定义, 但是仅有本处使用该函数, 故移至此处 */

{
	ENODE *expr = get_enode() ;

	expr->next = next ;
	expr->res  = res ;
	expr->tac  = code ;

	return expr ;

}	/* ENODE *mkenode( ENODE *next,
			   SYMB  *res,
			   TAC   *code ) */


void  yyerror( char *str )

/* YACC的标准出错处理函数, 分析器在分析出错时调用该函数 */

{
	error( str ) ;

}	/* void  yyerror( char *str ) */

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -