前言
前面做起来挺快的!接下来这个可能要花一周的时间了!需要构造一颗语义树,用来控制解释语句的顺序!
分割语句
主要就只有两种语句,一是普通的语句,二是在 if() 中的语句。下面规定
- 设置一个小括号计数器,初始化零,遇到左括号加一,遇到右括号减一。当计数器为一时,说明第二种语句开始了,当计数器为零时,第二种语句结束了。
- 设置一个第二种语句的标志,当此时是在分割第二种语句时,设置此标志为真。分割完第二种语句后,设置为假,即可以开始第一种语句的匹配。第一种语句以分号结尾。
//main.suatin
sum = 0;
if(1==TRUE and (1+2) > 45)
sum = 1 + 1;
elif(1+3+4)
sum = 2+2;
sum = sum +1;
sum = sum + 1;
end
//Resolver.cpp
void Resolver::create_ast() {
bool judge_statement_flag = false;
auto print = [&start,&end]() {
for (int k = start; k <= end; ++k) {
std::cout << global_infix[k]->name;
}
std::cout << "\n";
};
for (int it = 0; it < global_infix.size(); ++it) {
//处理Token和keyword
(this->*funcMap[global_infix[it]->type])(it);
if (judge_statement_flag) {
//()内的语句
//遇到if后面的右括号
if (count_little == 0) {
end = it - 1;//在右括号之前结束
std::cout << "括号内语句>";
print();
judge_statement_flag = false;
//之后的普通语句在右括号之后开始
start = it + 1;
end = it;
}
}
else {
//普通语句
if (global_infix[it]->type == SuatinTokenType_Sem) {
std::cout << "普通语句>";
print();
//之后的普通语句在分号之后开始
start = end + 1;
}
//遇到if后面的左括号
if (count_little == 1) {
start = end = it + 1;//在左括号之后开始
judge_statement_flag = true;
}
}
++end;
//每次循环开始和最后,end=it
}
g_error_lex_flag = true;//测试用
if (g_error_lex_flag)return;//词法期出错就没必要再构造语法树了!!!
//根据Parser构造语法树
g_run_time = SuatinRunTimeType_Parse;
g_statement_index = 0;
for (std::vector<Parser*>::iterator it = v_exprs.begin(); it != v_exprs.end(); ++it) {
(*it)->CreateASTree();
++g_statement_index;
}
}
void Deal_k_else(int& _t){
++start;//else不是语句的内容
...
}
void Deal_k_end(int& _t){
++start;//end不是语句的内容
...
}
普通语句>sum=0;
括号内语句>1==TRUEand(1+2)>45
普通语句>sum=1+1;
括号内语句>1+3+4
普通语句>sum=2+2;
普通语句>sum=sum+1;
普通语句>sum=sum+1;
解释器的类
大更改——用enum替换typeid
before
virtual NumExpr* GetClassType(){
return this;}
if(typeid(*(node->GetClassType())) == typeid(NumExpr)){
xxx
}
now
virtual SuatinExprClassType GetClassType(){
return SuatinExprClassType_NumExpr;}
if(node->GetClassType == SuatinExprClassType_NumExpr){
xxx
}
消灭布尔能够被计算的BUG
- 在+,-,*,/,^,中检查左边的ID,即exprRoot是否是布尔
- 在分号、and、or中检查右边的ID,即exprRoot是否是布尔
//1.检查左边的ID是否是布尔,布尔是不能用来计算的
if ((*_node)->GetClassType() == SuatinExprClassType_IDExpr) {
IDExpr* node_tmp = dynamic_cast<IDExpr*>(*_node);
if (node_tmp->GetType() == SuatinIDType_bool) {
ThrowException<SuatinErrorType_Syntax>(start, end, "[pow] boolean identifier cannot used for calculate");
return;
}
}
...
//2.检查右边的ID是否是布尔,布尔是不能用来计算的
if((*_exprRoot)->GetClassType() == SuatinExprClassType_IDExpr ){
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;
}
}
大更改——Run-Time Type Identification
之前构造语法树的时候,在构造的时候确定了==,~=,and,or,not的解释接口,这样在解释的时候就能快捷又准确。但是碰到多行语句的时候出问题了,比如一个变量sum,开始时没解释,在每个有sum变量的语法树中,sum的类型都是nil
sum = 1
if(sum == 2)
xxx
end
当解释if中的条件时,因为sum是nil 类型,所以 == 左边的解释接口就被设置成了bool类型的了,那么就是默认2是否是真或假了,这里2肯定是真,并且sum是1,也是真,必然执行if的语句块!!!!
所以,确定解释接口,不能在构造的时候确定,要在解释前,构造后确定!!!
//Parser.cpp
/*解释的第一步,确定Abstract Syntax Tree上==,~=,and,or,not的解释接口类型*/
void Parser::Confirm_ASTree_InterfaceType() {
if (root == NULL || exprNoVal == NULL) {
ThrowException<SuatinErrorType_Syntax>("root node was null");
return;
}
_fact_Confirm_ASTree_InterfaceType(exprNoVal);//开始迭代
}
//实际的确定接口类型的函数
void Parser::_fact_Confirm_ASTree_InterfaceType(Expr* _node) {
if (_node == NULL)return;
switch (_node->GetClassType()) {
case SuatinExprClassType_NotExpr:
{
NotExpr* node_tmp = dynamic_cast<NotExpr*>(_node);
//确定not的解释接口类型
node_tmp->Set_InterfaceType(GetTree_InterfaceType(node_tmp));
_fact_Confirm_ASTree_InterfaceType(node_tmp->GetContent());
}
break;
case SuatinExprClassType_AndExpr:
{
//DLR遍历
AndExpr* node_tmp = dynamic_cast<AndExpr*>(_node);
//确定左边的解释接口类型
node_tmp->SetLeft_InterfaceType(GetTree_InterfaceType(node_tmp->GetLeft()));
_fact_Confirm_ASTree_InterfaceType(node_tmp->GetLeft());
//确定右边的解释接口类型
node_tmp->SetRight_InterfaceType(GetTree_InterfaceType(node_tmp->GetRight()));
_fact_Confirm_ASTree_InterfaceType(node_tmp->GetRight());
}
break;
case SuatinExprClassType_OrExpr:
{
OrExpr* node_tmp = dynamic_cast<OrExpr*>(_node);
//确定左边的解释接口类型
node_tmp->SetLeft_InterfaceType(GetTree_InterfaceType(node_tmp->GetLeft()));
_fact_Confirm_ASTree_InterfaceType(node_tmp->GetLeft());
//确定右边的解释接口类型
node_tmp->SetRight_InterfaceType(GetTree_InterfaceType(node_tmp->GetRight()));
_fact_Confirm_ASTree_InterfaceType(node_tmp->GetRight());
}
break;
case SuatinExprClassType_EqEqExpr:
{
EqEqExpr* node_tmp = dynamic_cast<EqEqExpr*>(_node);
//确定左边的解释接口类型
node_tmp->SetLeft_InterfaceType(GetTree_InterfaceType(node_tmp->GetLeft()));
_fact_Confirm_ASTree_InterfaceType(node_tmp->GetLeft());
//确定右边的解释接口类型
node_tmp->SetRight_InterfaceType(GetTree_InterfaceType(node_tmp->GetRight()));
_fact_Confirm_ASTree_InterfaceType(node_tmp->GetRight());
}
break;
case SuatinExprClassType_NeqExpr:
{
NeqExpr* node_tmp = dynamic_cast<NeqExpr*>(_node);
//确定左边的解释接口类型
node_tmp->SetLeft_InterfaceType(GetTree_InterfaceType(node_tmp->GetLeft()));
_fact_Confirm_ASTree_InterfaceType(node_tmp->GetLeft());
//确定右边的解释接口类型
node_tmp->SetRight_InterfaceType(GetTree_InterfaceType(node_tmp->GetRight()));
_fact_Confirm_ASTree_InterfaceType(node_tmp->GetRight());
}
break;
case SuatinExprClassType_EqExpr:
ThrowException<SuatinErrorType_Type>("Expr pointer was evaluate type");
return;
default:
break;
}
}
if-elif-else实现原理
- 一级语句与一级的 if 块都是放在semantic_tree里的容器中! 没有结束的 if 块都放在uncompleted_leaf中!
- if 中有条件语句、有块语句、有last链条!解释if时,如果先解释其条件语句并得到该语句的返回真假,如果为真就执行if块中的语句——如果其中有嵌套的if的话,同样的原理——如果为假,就顺着last 链条找下一个elif/else,遇到了elif 的话,原理和遇到 if一样,如果前面都是假,遇到else就直接执行else语句块
- 单个if语句块用elif/else/end结束,elif语句块用elif/else/end结束,else语句块用end结束。整个if语义树用end结束!
最近匹配原则
遇到elif时,说明同一链条上的if块结束了(一条if-elif-else链条上if只有一个)。
遇到下一个elif/else时,说明同一链条上最近的一个if/elif结束了。
遇到end时,说明同一链条上最近的一个if/elif/else结束了!
通信机制
每条语句构造完后都放在Parser实例内,一条语句一个Parser实例,所有的Parser实例都放在Resolver的容器中!
语义树的解释接口也是无形参,无返回值的,这样的接口比较简洁!在Resolver中构造语义树,语义树中的每个节点都不拥有语句,只拥有每条语句在Parser容器中的索引!!!
因为语义树和语句的分离,所以不通过参数来解释!而通过做一个简单的信号槽机制,意思是做一个信号类、一个函数容器类,然后通过把要操作的函数通过信号类实例放入函数容器中,然后就可以通过信号类实例调用到该函数了!
——说是通信机制,其实本质还是利用全局变量,所以用一个全局的函数指针指向要操作的函数,然后再别的地方使用,这和信号槽机制一个意思!
——但是这样看起来简洁点!代码是给人看的,如果要追求性能,就不给人看了!
//Utils.h
...
//Suatin的slot/signals机制
#define emit /*只是表示这个已经被定义了,没有别的意思*/
#define slots
#define signals public
//非成员信号 -> 非成员函数
//非成员信号 -> 成员函数
#define Connect(signal,slot) ((signal).Bind(slot))
...
//slot
template<typename ... Args>
class SuatinSlot {
public:
using FuncPtr = std::function<void(Args ...)>; //重命名函数容器
private:
FuncPtr funcPtr = NULL;
public:
SuatinSlot(const FuncPtr& _funcPtr) : funcPtr(_funcPtr){
} //传入待执行的方法
void Exec(Args ... args) {
funcPtr(std::forward<Args>(args)...); //执行绑定的方法
}
};
//signal
template<typename ... Args>
class SuatinSignal {
public:
using SlotPtr = std::shared_ptr<SuatinSlot<Args...>>;//重命名Slot的智能指针
using FuncPtr = std::function<void(Args ...)>; //重命名函数容器
private:
std::vector<SlotPtr> v_slots;
public:
void Bind(const FuncPtr& _funcPtr) {
//绑定接受者和方法
SuatinSlot<Args...>* p = new SuatinSlot<Args...>(_funcPtr); //生成一个slot实例
v_slots.push_back(SlotPtr(p)); //放入智能指针,并把智能指针放入slot容器中
}
//发射信号
void operator()(Args...args) {
//void Emit(Args ... args){
for (auto& it : v_slots) {
it->Exec(std::forward<Args>(args)...);//执行所有绑定的方法,因为一个信号能触发多个动作
}
}
};
//语义树发给Resolver的信号
extern SuatinSignal<int>* g_signal;
//Resolver.cpp
Resolver::Resolver(){
...
//初始化信号与槽
g_signal = new SuatinSignal<int>();
Connect(*g_signal, std::bind(&Resolver::_slot_interpret, this, std::placeholders::_1));
...
}
...
//接收从语义树中发射的信号
void Resolver::_slot_interpret(int _index) {
if (CheckStatementIndex(_index) == false)return;
v_exprs[_index]->interpret();
}
bool Resolver::CheckStatementIndex(int _index) {
//检查索引的范围
if (_index < 0 || _index >= v_exprs.size()) {
std::string s = "index = " + std::to_string(_index) + " was wrong";
ThrowException<SuatinErrorType_OutRange>(s);
return false;
}
return true;
}
//Cmd.cpp
void SingleCmd::interpret() {
emit (*g_signal)(index); //发射信号到Resolver中的解释方法
}
构造if-elif-else嵌套语义树(N叉树)
全局的Block下有N条语句,每条语句都可能有一个if节点,每一个if节点都可能有一条if-elif-…-else链条,每个链条上的节点都有一个Block,每个Block都有M条语句。。。
所以,这么多分叉,遍历起来太麻烦了!!!需要用栈来储存Block,每次遇到if之类的节点,或者是遇到单条块语句,都放在当前的Block里,当前的Block就是未结束的Block,一旦结束就从栈中抛出,取下一个Block作为当前的Block!!!这样就不管是什么叉树,不管有多少层,多少分叉了!!!
- 准备一个临时变量uncompleted_tree,普通的语句都会被放在semantic_tree中的容器中,完整的if[-elif-else]-end块,也会被放在里面,在还没用变得完整的时候,就把其根节点先放在uncompleted_tree下面!
- 准备一个栈,放入没有结束的语句块。比如if,在遇到下一个elif或else或end前,都是没有结束的,这时候如果遇到普通语句,就把该语句装入栈顶节点的block中就行了!!!如果遇到的是括号内语句,就设置为栈顶节点的condition了!!!
- 遇到结束符的栈顶节点,end_flag标志设置为真,并出栈。当栈里没有节点后,就将uncompleted_tree装入semantic_tree中的容器里,置空uncompleted_tree
项目演示
//main.suatin
sum = not TRUE;
if(sum==TRUE)
sum = -1;
elif(sum)
sum = -2;
else
sum = -3;
if(sum==-3)
sum = -4;
if(sum==-4)
sum = -5;
end
end
end
初始化语言环境 time consumed 0 ms
词法分析 time consumed 813 ms
普通语句>sum=notTRUE;
括号内语句>sum==TRUE
普通语句>sum=-1;
括号内语句>sum
普通语句>sum=-2;
普通语句>sum=-3;
括号内语句>sum==-3
普通语句>sum=-4;
括号内语句>sum==-4
普通语句>sum=-5;
语法分析·创建语法树 time consumed 131 ms
[result]false
[result]false
[result]false
[result]-3
[result]true
[result]-4
[result]true
[result]-5
语法分析·解释语法树 time consumed 28 ms
suatin environment>
name isconst type funcPtr flag num str
NIL true nil 00000000 false 0
FALSE true bool 00000000 false 0
TRUE true bool 00000000 true 0
sum false number 00000000 true -5
显示环境信息 time consumed 225 ms
释放环境 time consumed 0 ms
program time consumed 1229 ms
请按任意键继续. . .
项目代码地址CSDN
https://download.csdn.net/download/weixin_41374099/12255241
项目代码地址BDWP
链接:https://pan.baidu.com/s/1KXHyGx8Xelkx23TCoE5FkA
提取码:zut6
参考:
C++11实现Qt的信号机制