前言
赋值节点的interpret接口、interpet_str接口、interpret_bool接口不是并列的关系……
这个自制编程语言的项目中,已经遇到太多的不对称的东西了,明明是相似的接口,相似的类型,但是实现却特别冗余!没办法把两个相似的函数放在一起比较,这说明一个问题,我的项目架构不好,有很多辣鸡代码!
现在来解决这个问题!
设置firstId_is_bool_flag标志
项目中的数据类型:int/number/nil/bool/string
底层数据类型:double/bool/string
项目中的Token有下面三种情况
- 数字、数字类型的id
- 字符串、字符串类型的id
- 布尔类型的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
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类内部操作的,所以需要全局变量拿到内部的语句开始和结尾的信息!!!