lang:自制編程語言6——語法樹引入邏輯運算和關鍵字方法

前言

三千行代碼,寫了個寂寞。
做了這麼幾個項目,一直都在操作語法樹,我也不知道怎樣才寫的好,之前的任務都完成了是因爲複雜的我都跳過了(汗)多虧了VS的調試很強大,懂得使用SHIFT+F9的才能熟練掌握C++內存管理(誤)。

之前的判斷運算我取了巧,讓判斷式只能有一個判斷符,一般是沒問題的,但是引入邏輯運算就有問題了!一個表達式(無賦值)可以出現多個判斷符、多個邏輯符、五則運算、字符串、判斷符和邏輯符混在一起這些情況都要考慮清楚——現在還沒到要轉方向的時候,一點點摸索着!!!


改進字符串拼接

之前都字符串拼接模式,是在completedName_flag爲真,即定義了變量之後,碰見的第一個Token是Str時設置的。並且字符串拼接模式下,不能除了加號和字符串本身,不能有其他符號在字符串拼接的表達式中!

改進之後,1.允許定義變量後的第一個Token是Id,當此Id是字符串類型時,設置表達式爲字符串拼接模式。
2.字符串拼接模式下的表達式可以有類型爲字符串的Id存在!

void Parser::DealToken_Id(int& _t) {
		if (exprType == ExprType_StringConnect) {
			/*即出現了這種情況:"xxx"+a
				其中a是字符串類型
				       +           ->             +
				    /    \                      /   \
		    	"xxx"      None             "xxx"    a
			*/
			if (expTmp == NULL) { //不存在待處理的節點
				ThrowException<SuatinErrorType_NoDefined>("[Id] string connect expression wrong");
				return;
			}
            //字符串拼接模式,不處理非Str類型的id
			if (SuatinEnv[global_infix[_t]->name]->type != SuatinIDType_string) {
				return;
			}


			SymbolExpr* node_tmp = dynamic_cast<SymbolExpr*>(expTmp);
			node_tmp->SetRight(new IDExpr(global_infix[_t]->name));
			expTmp = NULL;

			return;
		}



		//處理關鍵字
		if (global_infix[_t]->k_type != SuatinKeyWordType_NOT_KEYWORD) {
			(this->*k_funcMap[global_infix[_t]->k_type])(_t);
			return;
		}


		//存在待處理的節點
		if (expTmp) {
			if (typeid(*(expTmp->GetClassType())) == typeid(LLeftExpr)) {//待處理節點是左括號節點
				LLeftExpr* node_tmp = dynamic_cast<LLeftExpr*>(expTmp);
				node_tmp->SetContent(new IDExpr(global_infix[_t]->name));
			}
			else {
				SymbolExpr* node_tmp = dynamic_cast<SymbolExpr*>(expTmp);
				node_tmp->SetRight(new IDExpr(global_infix[_t]->name));
				expTmp = expTmpLeft;//上移待處理節點的指針
			}
			return;
		}

		
		//不存在待處理的節點
		exprRoot = new IDExpr(global_infix[_t]->name);
		
		//檢查一下,表達式的開頭第一個id的類型,如果是字符串類型就轉變表達式類型爲字符串拼接模式
		if (firstId_is_string_flag) {
			if (SuatinEnv[global_infix[_t]->name]->type == SuatinIDType_string) {
				exprType = ExprType_StringConnect;
				firstId_is_string_flag = false;
			}
		}
	}

解釋器的類

目前24個類,類多不多到是無所謂,只是代碼是真的亂!!!
在這裏插入圖片描述

Parser類

這次多加了二十多個函數,用來處理關鍵字!在進行詞法分析的時候,關鍵字和變量我都當成是標識符,在遍歷中綴表達式時,遇到Id會進入DealToken_Id函數,在其中判斷是否是關鍵字並進入對應函數!

#pragma once
#ifndef _PARSER_H_
#define _PARSER_H_
#include"Expr.h"



namespace sua {

	//成員函數指針,如果不加上Parser::修飾,就只能把對應的成員函數都改成靜態的!!!
	class Parser;
	typedef void(Parser::*DealFuncPtr)(int&);



	//語法解析器
	class Parser {
	private:

		//處理Token的函數
		void DealToken_Num(int& _t);
		void DealToken_Id(int& _t);
		void DealToken_Str(int& _t);
		void DealToken_Pow(int& _t);
		void DealToken_Mul(int& _t);
		void DealToken_Div(int& _t);
		void DealToken_Add(int& _t);
		void DealToken_Sub(int& _t);
		void DealToken_Gre(int& _t);
		void DealToken_GreEq(int& _t);
		void DealToken_Les(int& _t);
		void DealToken_LesEq(int& _t);
		void DealToken_Neq(int& _t);
		void DealToken_EqEq(int& _t);
		void DealToken_Eq(int& _t);
		void DealToken_Com(int& _t);
		void DealToken_ML(int& _t);
		void DealToken_MR(int& _t);
		void DealToken_LL(int& _t);
		void DealToken_LR(int& _t);
		void DealToken_BL(int& _t);
		void DealToken_BR(int& _t);
		void DealToken_Dot(int& _t);
		void DealToken_Sem(int& _t);
		//void DealToken_Eol(int& _t);


		
		//處理關鍵字的函數
		void Deal_k_if(int& _t);
		void Deal_k_elif(int& _t);
		void Deal_k_else(int& _t);
		void Deal_k_for(int& _t);
		void Deal_k_break(int& _t);
		void Deal_k_continue(int& _t);
		void Deal_k_do(int& _t);
		void Deal_k_until(int& _t);
		void Deal_k_while(int& _t);
		void Deal_k_local(int& _t);
		void Deal_k_const(int& _t);
		void Deal_k_and(int& _t);
		void Deal_k_or(int& _t);
		void Deal_k_not(int& _t);
		void Deal_k_function(int& _t);
		void Deal_k_end(int& _t);
		void Deal_k_return(int& _t);

					

		
		//創建語法樹
		void SetupASTree(int start, int end);//實際的創建函數
		//打印漂亮的二叉樹
		void DisplayASTree(Expr* _node, int _num = 0);//實際打印的函數
		//刪除二叉樹
		void DelTree(Expr* _expr);
		
	public:
		Parser();
		~Parser();


		//創建語法樹
		void CreateASTree();//留給外面的接口
		//打印漂亮的語法樹
		void ShowASTree();//留給外面的接口
		//解釋語法樹
		void interpret();
		//是否已經構造完成
		bool GetCompletedASTreeFlag()const;


	private:
		bool   completedASTree_flag = false;//是否已經完成語法樹的構造
		int  v_forDisplayASTree[100] = { 0 };//打印語法樹要用的工具
		bool firstEq_flag = true;//是否第一次遇到等於號
		bool firstId_is_string_flag = true;	//表達式的第一個Id是否是flag
		std::map<SuatinTokenType, DealFuncPtr> funcMap;//爲了減少if-else的個數,使用查表的方法,根據不同的枚舉來調用不同的函數
		std::map<SuatinKeyWordType, DealFuncPtr> k_funcMap;//處理keyword的函數表


		ExprType exprType = ExprType_NumberCalculate;//表達式類型,默認是數字運算模式,只有第一個Token是字符串時會變爲字符串拼接模式
		
		
		bool   completedName_flag = false;//是否完成左邊的變量的聲明		
	
		

								 
		
		/*root不爲空時語句是賦值語句,exprRoot不爲空時是表達式,root和exprRoot在構造完語法樹後不能同時爲空也不能同時不爲空*/
		//與exprRoot進行交互
		Expr* root = NULL;											//語法樹根節點
		

		/*logicRoot不爲空時語句和exprRoot不爲空時一樣,都是表達式!!!*/
		//與judgeRoot進行交互,最後用exprRoot去替換掉logicRoot
		Expr*  logicRoot = NULL;								//邏輯式語法樹根節點


		/*judgeRoot不爲空時語句和exprRoot不爲空時語句一樣,都是表達式!!!	*/
		//judgeRoot與exprRoot進行交互,最後用exprRoot去替換掉judgeRoot
		Expr* judgeRoot = NULL;								//判斷式語法樹根節點
											



		//普通表達式要構造語法樹用得到的指針
		Expr* exprRoot = NULL;													//表達式語法樹根節點
		Expr* expTmp = NULL;													//待處理的節點(爲空)的父節點
		Expr* expTmpLeft = NULL;												//最下層的沒匹配到右括號的左括號節點
		std::stack<Expr*>  v_NoMatchedLLeft;							//保存所有的未匹配到右括號的左括號節點
			
		
	};

};
#endif

目前構造語法樹的邏輯

  1. 一條語句,可以不是賦值語句,即root爲空
  2. 賦值語句後面可以跟着賦值語句,最後面的語句是表達式
  3. 一條語句以分號結尾
  4. 表達式有4種模式,分別是五則運算、字符串拼接、判斷運算、邏輯運算
  5. 幾乎每一個Token都要區分一下幾種表達式模式
  6. 表達式模式除了有特定的標誌外,與exprRoot、judgeRoot、logicRoot是否爲空有關
  7. 每次遇到一個等於號,都要把這個等於號掛載在root的語法樹中。賦值語法樹除了第一個賦值是左結合的外,後面跟着的其他賦值都是右結合的,即每假如一個賦值就要查找root語法樹最右的賦值節點,並把新樹掛在它右孩子上。
  8. 賦值節點的左邊一定是變量節點
  9. 賦值節點最後一個孩子,在出現分號後,將exprRoot(簡單表達式)掛在root語法樹中
  10. 沒有邏輯運算的式子裏只能有一個判斷節點,6種判斷節點優先級一樣
  11. 當出現判斷節點後,將exprRoot掛在判斷節點左孩子上,當出現邏輯節點或分號時,才把新的exprRoot掛在此判斷節點的有孩子上
  12. 判斷模式的句子遇到分號後,會先裝載好judeRoot語法樹,然後用exprRoot替換掉judgeRoot,這樣的話判斷模式就不會和賦值語句有直接交互!!!
  13. 當出現邏輯節點後,將exprRoot掛在邏輯節點左孩子上,當出現邏輯節點或分號時,先執行第11條,才把新的exprRoot掛在此邏輯節點的有孩子上
  14. 邏輯模式的句子遇到分號後,先執行第12條,再裝載好logicRoot語法樹,然後用exprRoot替換掉logicRoot,這樣的話邏輯模式就不會和賦值語句有直接交互!!!
  15. 無論是判斷模式還是邏輯模式,除了表達式可以有括號外,其他位置不能有括號!
  16. 邏輯模式中,not只能有一個,必須是表達式開頭第一個
  17. 邏輯模式下,or優先級比and低
//下面的語句都是合法的
1 + 2 * (3 / 4) ^5 - 100;
a = TEST_C + "AAAAA" +"SSSSS";//TEST_C是預定義變量,用了測試的,前一篇有提到   
//返回author@DemllieAAAAASSSSS
a = b = c = 2;
a = b = 3 + 1 > 5 * 8 ;//返回false
a = 1 >2 and 2 ~= 3 or 4<5 and 5 ==6;//返回false
a = not 1 > 3;//返回true
not 1~=2 and 3~= 4;//返回false

項目演示

項目代碼在我上傳的資源裏!!!

文件中內容: a = not 1 < 2 + 1 and 4 == 5 or 2~=1;

詞法分析 time consumed 211 ms
初始化語言環境 time consumed 1 ms
語法分析·構造語法樹 time consumed 1 ms
------------------------------------------------------------
=
├── a
└── not
    └── or
        ├── and
        │   ├── <
        │   │   ├── 1
        │   │   └── +
        │   │       ├── 2
        │   │       └── 1
        │   └── ==
        │       ├── 4
        │       └── 5
        └── ~=
            ├── 2
            └── 1
------------------------------------------------------------
語法分析·顯示語法樹 time consumed 39 ms
result = false
語法分析·解釋語法樹 time consumed 1 ms
語言環境>
                name   isconst    type      funcPtr     flag             num   str
                 NIL    true       nil     00000000    false               0
              TEST_C    true       int     00000000     true               7
               FALSE    true      bool     00000000    false               0
              TEST_D    true      bool     00000000    false               0
                TRUE    true      bool     00000000     true               0
                   a   false      bool     00000000    false               0
              TEST_B    true    number     00000000     true     1.22323e+06
              TEST_A    true    string     00000000     true               0   author@Demllie
顯示環境信息 time consumed 650 ms

項目代碼地址CSDN
http://download.csdn.net/download/weixin_41374099/12203591
項目代碼地址BDWP
鏈接:https://pan.baidu.com/s/1S4e1KvZ5uv8XBAkwUvklEA
提取碼:i72f

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