前言
三千行代碼,寫了個寂寞。
做了這麼幾個項目,一直都在操作語法樹,我也不知道怎樣才寫的好,之前的任務都完成了是因爲複雜的我都跳過了(汗)多虧了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
目前構造語法樹的邏輯
- 一條語句,可以不是賦值語句,即root爲空
- 賦值語句後面可以跟着賦值語句,最後面的語句是表達式
- 一條語句以分號結尾
- 表達式有4種模式,分別是五則運算、字符串拼接、判斷運算、邏輯運算
- 幾乎每一個Token都要區分一下幾種表達式模式
- 表達式模式除了有特定的標誌外,與exprRoot、judgeRoot、logicRoot是否爲空有關
- 每次遇到一個等於號,都要把這個等於號掛載在root的語法樹中。賦值語法樹除了第一個賦值是左結合的外,後面跟着的其他賦值都是右結合的,即每假如一個賦值就要查找root語法樹最右的賦值節點,並把新樹掛在它右孩子上。
- 賦值節點的左邊一定是變量節點
- 賦值節點最後一個孩子,在出現分號後,將exprRoot(簡單表達式)掛在root語法樹中
- 沒有邏輯運算的式子裏只能有一個判斷節點,6種判斷節點優先級一樣
- 當出現判斷節點後,將exprRoot掛在判斷節點左孩子上,當出現邏輯節點或分號時,才把新的exprRoot掛在此判斷節點的有孩子上
- 判斷模式的句子遇到分號後,會先裝載好judeRoot語法樹,然後用exprRoot替換掉judgeRoot,這樣的話判斷模式就不會和賦值語句有直接交互!!!
- 當出現邏輯節點後,將exprRoot掛在邏輯節點左孩子上,當出現邏輯節點或分號時,先執行第11條,才把新的exprRoot掛在此邏輯節點的有孩子上
- 邏輯模式的句子遇到分號後,先執行第12條,再裝載好logicRoot語法樹,然後用exprRoot替換掉logicRoot,這樣的話邏輯模式就不會和賦值語句有直接交互!!!
- 無論是判斷模式還是邏輯模式,除了表達式可以有括號外,其他位置不能有括號!
- 邏輯模式中,not只能有一個,必須是表達式開頭第一個
- 邏輯模式下,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