lang:自制編程語言11——if-elif-else

前言

前面做起來挺快的!接下來這個可能要花一週的時間了!需要構造一顆語義樹,用來控制解釋語句的順序!


分割語句

主要就只有兩種語句,一是普通的語句,二是在 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

  1. 在+,-,*,/,^,中檢查左邊的ID,即exprRoot是否是布爾
  2. 在分號、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的信號機制

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章