【Suatin】不学编译原理就制作语言10——改进赋值

前言

赋值节点的interpret接口、interpet_str接口、interpret_bool接口不是并列的关系……

这个自制编程语言的项目中,已经遇到太多的不对称的东西了,明明是相似的接口,相似的类型,但是实现却特别冗余!没办法把两个相似的函数放在一起比较,这说明一个问题,我的项目架构不好,有很多辣鸡代码!

现在来解决这个问题!


设置firstId_is_bool_flag标志

项目中的数据类型:int/number/nil/bool/string
底层数据类型:double/bool/string

项目中的Token有下面三种情况

  1. 数字、数字类型的id
  2. 字符串、字符串类型的id
  3. 布尔类型的id

之前为了将简单表达式从语句中抽离出来,就是用and , >= 之类的符号,分割简单表达式,将其作为判断式或逻辑式的单元!!!
为此引入了firstId_is_string_flag,因为要靠简单表达式开头的数据,区分简单表达式的模式,是否是字符串拼接模式!!!

a = TRUE; 这样的语句会调用到EqExpr的interpret接口,还会被当成计算式计算。也就是出现下面这张情况

a = 1 + TRUE;
[result]1

所以,规定

  • 如果表达式是简单表达式,并且第一个是bool类型的id,那么,就准备退出——此id后面如果不是分号,就报错!如果是分号就如愿退出!
	void Parser::CreateASTree() {
    
    
		//遍历全局中缀表达式
		try {
    
    
			for (int it = start; it <= end; ++it) {
    
    
				(this->*funcMap[global_infix[it]->type])(it);//处理Token和keyword

				if (end_flag)break;//遇到分号跳出
				
				if (firstId_is_bool_flag) {
    
    //表达式第一个id是bool类型的变量
					if (global_infix[ ++ it]->type != SuatinTokenType_Sem) {
    
    //如果下一个不是分号
						ThrowException<SuatinErrorType_Syntax>(start,end,"boolean identifier cannot used for calculate");
						return;
					}
					DealToken_Sem(it);
					break;
				}
			}

			if (root == NULL) ThrowException<SuatinErrorType_Value>(start,end,"abstract syntax tree was none");
			/*
			judgeRoot <-> exprRoot
			logicRoot  <->  exprRoot
			exprRoot  <->  root 
			最后除了root外,其他指针都为空!
			*/

			CheckASTreeSyntax();

		}
		catch (SuatinExcept& e) {
    
    
			std::cout << e.what();
		}
		catch (...) {
    
    
			std::cout << "parser analysis error";
		}
	}
	
	void Parser::DealToken_Id(int& _t){
    
    
		
		...
		
		//检查一下,表达式的开头第一个id的类型,如果是bool类型就准备退出构造语法树的阶段
		if (firstId_is_bool_flag == false && m_exprType == ExprType_Simple) {
    
    
			if (SuatinEnv[global_infix[_t]->name]->type == SuatinIDType_bool) {
    
    
				firstId_is_bool_flag = true;
			}
		}
	}
	void Parser::interpret() {
    
    


		try {
    
    

			if (m_exprType == ExprType_Simple) {
    
     //简单表达式
				if (firstId_is_bool_flag) {
    
      //开头第一个是bool类型的id
					bool result = root == NULL ? true : root->interpret_bool();
					std::cout << "[result]" << BOOL2STR(result) << std::endl;
				}
				else if (m_simpleExprType == SimpleExprType_NumCalc) {
    
     //五则计算式
				...
	...

使EqExpr的三个解释接口并列

因为五则计算式中只有数字、字符串拼接式中只有字符串、开头bool或判断式或逻辑式的返回只有布尔,所以解释起来,清楚多了!
果然是自己辣鸡代码写多了!

	//处理数字、数字类型的id--------------------
	double EqExpr::interpret() {
    
    
		double rightValue = right->interpret();//递归进去
		

		把右边的变量的值拷贝给左边的变量,因为是对符号表的操作,不改变指针,所以传入一级指针就好!!!
		//auto copyBlock = [](Expr* _left,Expr* _right) {
    
    
		//	IDExpr* dst = dynamic_cast<IDExpr*>(_left);
		//	IDExpr* src = dynamic_cast<IDExpr*>(_right);
		//	CopyID(dst->GetName(), src->GetName());
		//};

		赋值右边的表达式只有单个Id
		//if (typeid(*(right->GetClassType())) == typeid(IDExpr)) { //右孩子只有个变量节点!!!
		//	copyBlock(left, right);
		//	return rightValue;
		//}
		//else if (typeid(*(right->GetClassType())) == typeid(EqExpr)) { //右孩子同样是个赋值节点!!!

		//	SymbolExpr* node_tmp = dynamic_cast<SymbolExpr*>(right);
		//	copyBlock(left, node_tmp->GetLeft());
		//	return rightValue;
		//}
		
		IDExpr* var = dynamic_cast<IDExpr*>(left);
		var->SetNumber(rightValue);
		return rightValue;
	}


	//处理字符串、字符串类型的id------------------
	std::string EqExpr::interpret_str() {
    
    
		std::string rightValue = right->interpret_str();//递归进去


		把右边的变量的值拷贝给左边的变量,因为是对符号表的操作,不改变指针,所以传入一级指针就好!!!
		//auto copyBlock = [](Expr* _left, Expr* _right) {
    
    
		//	IDExpr* dst = dynamic_cast<IDExpr*>(_left);
		//	IDExpr* src = dynamic_cast<IDExpr*>(_right);
		//	CopyID(dst->GetName(), src->GetName());
		//};

		赋值右边的表达式只有单个Id
		//if (typeid(*(right->GetClassType())) == typeid(IDExpr)) { //右孩子只有个变量节点!!!
		//	copyBlock(left, right);
		//	return rightValue;
		//}
		//else if (typeid(*(right->GetClassType())) == typeid(EqExpr)) { //右孩子同样是个赋值节点!!!

		//	SymbolExpr* node_tmp = dynamic_cast<SymbolExpr*>(right);
		//	copyBlock(left, node_tmp->GetLeft());
		//	return rightValue;

		//}


		IDExpr* var = dynamic_cast<IDExpr*>(left);
		var->SetString(rightValue);
		return rightValue;
	}

	//bool类型的id--------------------
	bool EqExpr::interpret_bool() {
    
    
		bool rightValue = right->interpret_bool();//递归进去
		IDExpr* var = dynamic_cast<IDExpr*>(left);
		var->SetBool(rightValue);
		return rightValue;
	}

这样操作一番后,可以看出,开头bool、五则计算、字符串拼接这几个已经是并列的了,可以看做三个模式!

[项目代码下载地址]
https://download.csdn.net/download/weixin_41374099/12238776

扫描二维码关注公众号,回复: 11842871 查看本文章

BDWP
链接:https://pan.baidu.com/s/1uVVWEC8UAEYB7X1z2r8FBQ
提取码:85ep

   \;
   \;

去掉开头bool类型的特殊性

——2020.3.13
之前写完上面的内容觉得基本单行语句做完了,没错误了!其实不然,我将开头bool类型的语句单独拿出来也是有问题的!

a = TRUE == "hello";//error

不再允许上面的写法,所以,之前所思考的将开头bool的语句和其他两种模式并列的想法是完全不行的!!!!!!!!!!!去掉firstId_is_bool_flag,换个其他的方案!!!
也就是除了对EqExpr的解释接口的修改,上面的其他规定和修改都无效了!!!

在firstId是布尔的情况下

将之前的firstId_is_string_flag改为firstId_flag,记录表达式的开始的Id。
在字符串类型的判断旁加上对布尔的判断,如果是布尔的,就直接将表达式类型改为判断式,因为简单表达式中是不允许有布尔变量的!!!

	void DealToken_Id(int& _t){
    
    
		...
		//检查一下,表达式的开头第一个id的类型
		if (firstId_flag == false) {
    
    
			if (SuatinEnv[global_infix[_t]->name]->type == SuatinIDType_string) {
    
    
				m_simpleExprType = SimpleExprType_StrLink;//如果是字符串类型就转变表达式类型为字符串拼接模式
				firstId_flag = true;
			}
			else if (SuatinEnv[global_infix[_t]->name]->type == SuatinIDType_bool) {
    
    
				m_exprType = ExprType_Compare;//如果是布尔类型,表达式类型至少是判断式。这样就不会把当做简单表达式中的计算模式被interpret接口处理了
				firstId_flag = true;
			}
		}		
	}

>,<,>=,<=四种节点情况下

在出现这四种判断运算符的时候,要做判断exprRoot,即当前的判断符前面的表达式是否是一个布尔变量,如果是就报错!!!

	void DealToken_LesEq(int& _t){
    
    
		...
		//不存在待处理的节点------------------
		if (typeid(*(exprRoot->GetClassType())) == typeid(IDExpr)) {
    
     //左操作数是一个ID
			IDExpr* node_tmp = dynamic_cast<IDExpr*>(exprRoot);
			if (node_tmp->GetType() == SuatinIDType_bool) {
    
    
				ThrowException<SuatinErrorType_Syntax>(start, end, "[<=] boolean identifier cannot used for compare");
				return;
			}
		}
		judgeRoot = new LesEqExpr(exprRoot, NULL);
		exprRoot = NULL;
	}

还有就是逻辑运算符,遇到逻辑运算符后也要对judeRoot进行处理,其中处理前就要先判断下exprRoot是否是布尔型变量!!!

	void Deal_k_and(int& _t){
    
    
		...
		//把exprRoot装上judgeRoot
		if (judgeRoot) {
    
    
			//>,<,>=,<=这几个节点的情况下,左右操作数不能是布尔
			if (typeid(*(judgeRoot->GetClassType())) == typeid(GreExpr) || typeid(*(judgeRoot->GetClassType())) == typeid(GreEqExpr) || typeid(*(judgeRoot->GetClassType())) == typeid(LesExpr) || typeid(*(judgeRoot->GetClassType())) == typeid(LesEqExpr)) {
    
    
				if (typeid(*(exprRoot->GetClassType())) == typeid(IDExpr)) {
    
     //右操作数是一个ID
					IDExpr* node_tmp = dynamic_cast<IDExpr*>(exprRoot);
					if (node_tmp->GetType() == SuatinIDType_bool) {
    
    
						ThrowException<SuatinErrorType_Syntax>(start, end, "[or] boolean identifier cannot used for compare");
						return;
					}
				}
			}
			SymbolExpr* node_tmp = dynamic_cast<SymbolExpr*>(judgeRoot);
			node_tmp->SetRight(exprRoot);
			...
		...
	}

在最后遇到分号后,对judgeRoot装上右孩子时也要进行这种判断!!!

	void DealToken_Sem(int& _t){
    
    
		
		...
		
		auto judge_chunk = [this](Expr** _node,Expr** _exprRoot) {
    
    
			//>,<,>=,<=这几个节点的情况下,左右操作数不能是布尔
			if (typeid(*((*_node)->GetClassType())) == typeid(GreExpr) || typeid(*((*_node)->GetClassType())) == typeid(GreEqExpr) || typeid(*((*_node)->GetClassType())) == typeid(LesExpr) || typeid(*((*_node)->GetClassType())) == typeid(LesEqExpr)) {
    
    
				if (typeid(*( (*_exprRoot)->GetClassType())) == typeid(IDExpr)) {
    
     //右操作数是一个ID
					IDExpr* node_tmp = dynamic_cast<IDExpr*>((*_exprRoot));
					if (node_tmp->GetType() == SuatinIDType_bool) {
    
    
						ThrowException<SuatinErrorType_Syntax>(start, end, "[sem] boolean identifier cannot used for compare");
						return;
					}
				}
			}

			//替换judgeRoot
			SymbolExpr* node_tmp = dynamic_cast<SymbolExpr*>((*_node));
			node_tmp->SetRight((*_exprRoot));

			//确定解释接口类型(~=/==右边)
			if (typeid(*((*_node)->GetClassType())) == typeid(NeqExpr) || typeid(*((*_node)->GetClassType())) == typeid(EqEqExpr)) {
    
    
				InterfaceTypeExpr* node_tmp = dynamic_cast<InterfaceTypeExpr*>((*_node));
				node_tmp->SetRight_InterfaceType(GetTree_InterfaceType(&(*_exprRoot)));
			}

			(*_exprRoot) = (*_node);//根节点上移
			(*_node) = NULL;

		};
		
		...
		
	}

检查待处理节点的右孩子是否是布尔

	void DealToken_Id(int& _t){
    
    
		...
		//存在待处理的节点------------------
		if (expTmp) {
    
    
			//五则计算模式下,他类型的Id不做处理
			if (SuatinEnv[global_infix[_t]->name]->type != SuatinIDType_number) {
    
     
				ThrowException<SuatinErrorType_Syntax>(start, end, "[Id] boolean identifier cannot used for calculate");
				return;
			}
			...
		...
	}

在五则计算的模式下,当存在待处理节点时,这时候会判断待处理节点的右孩子是否是布尔变量!!!所以 a = 1+ TRUE; 这种代码也是能检查出错的,但是 a=TRUE + 1 检查不出来了!!!
在这里插入图片描述

^,*,/,+,-五种函数开头——这一步是错的

这样在遇到 a= TRUE+1; 这种语句的时候就会报错了!!!
这样会出现新的问题,当语句编程判断式或逻辑式后,后面的表达式就不能进行计算了!
允许布尔计算这个BUG,之后再解决!

	void DealToken_Pow(int&){
    
    
		if (m_exprType != ExprType_Simple) {
    
     //表达式类型为判断式或逻辑式
			ThrowException<SuatinErrorType_Syntax>(start, end, "[pow] wrong type identifier in expression");
			return;
		}
		...
	}

在这里插入图片描述
不幸的是,上面的错都找出来后,正常编写的第6条语句,也报错了!!!

初始化表达式

每次遇到判断符或逻辑符,都意味着下一个表达式要开始了,所以每个逻辑符、判断符方法里都要初始一些变量

		//初始化表达式(简单表达式的模式、firstId)
		m_simpleExprType = SimpleExprType_NumCalc;
		firstId_flag = true;

解释NIL要报错

一般计算式子里如果有NIL变量,都是当做值为0的数算的,这不行,所以要在解释时,将这种NIL类型的变量找出来,并抛出异常!!!(找出来也许是不必要的)

//Parser.cpp
	void Parser::interpret() {
    
    

		try {
    
    

			if (m_exprType == ExprType_Simple) {
    
     //简单表达式
				if (m_simpleExprType == SimpleExprType_NumCalc) {
    
     //五则计算式
					//先判断类型
					if (typeid(*(root->GetClassType())) == typeid(IDExpr)) {
    
     //表达式为单个ID
						IDExpr* node_tmp = dynamic_cast<IDExpr*>(root);
						if (node_tmp->GetType() == SuatinIDType_nil) {
    
    
							std::cout << "[result]nil\n";
							return;
						}
						else if (node_tmp->GetType() == SuatinIDType_bool) {
    
    
							std::cout << "[result]" << BOOL2STR(node_tmp->GetBool()) << std::endl;
							return;
						}
					}					
					double result = root == NULL ? 0 : root->interpret();
					std::cout << "[result]" << result << std::endl;
				}
				else if (m_simpleExprType == SimpleExprType_StrLink) {
    
     //字符串拼接式
					std::string result = root == NULL ? "" : root->interpret_str();
					std::cout << "[result]" << result << std::endl;
				}
			}
			else {
    
     //判断式或逻辑式
				bool result = root == NULL ? true : root->interpret_bool();
				std::cout << "[result]" << BOOL2STR(result) << std::endl;
			}
		}
		catch (SuatinExcept& e) {
    
    
			PrintException(e.what());
		}
		catch (...) {
    
    
			PrintException("interpret error\n");
		}
	}

//Expr.cpp

	//IDExpr的解释接口
	double IDExpr::interpret() {
    
    
		if (GetType() == SuatinIDType_nil) ThrowException<SuatinErrorType_Value>("nil type identifier cannot used");
		return GetNumber();
	}

	std::string IDExpr::interpret_str() {
    
    
		if (GetType() == SuatinIDType_nil) ThrowException<SuatinErrorType_Value>("nil type identifier cannot used");
		return GetString();
	}
	bool IDExpr::interpret_bool() {
    
    
		if (GetType() == SuatinIDType_nil) ThrowException<SuatinErrorType_Value>("nil type identifier cannot used");
		return GetBool();
	}

在这里插入图片描述

  • 解释出NIL异常的时候,不能得到打印出错误语句!!!——有一种拆了东墙补西墙的感觉,解决这个问题要重写异常函数了!!!

[项目代码下载地址2]
https://download.csdn.net/download/weixin_41374099/12245756

BDWP
链接:https://pan.baidu.com/s/1FgjPtVjND8mnUvSsJoTgvA
提取码:7slg

打印出计算了NIL的语句

再定义两个全局变量g_statement_start,g_statement_end,在Parser类的构造函数里初始化,因为解释语法树是从Parser类内部操作的,所以需要全局变量拿到内部的语句开始和结尾的信息!!!
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_41374099/article/details/104764825