前言
之前的文章
lang:談談自制編程語言
lang:C++自定義異常類——用來處理自制編程語言的異常信息
lang:計算器改進版本_默認函數_小數_指數
lang:計算器
lang:使用BNF範式設計一個文法
lang:總結9種編程語言的語法來設計自己的編程語言Suatin-lang
之前的項目重寫了,第一次寫的果然大部分都是辣雞代碼!!!沒用到的暫時不要寫,因爲之前學了點Java,粘染了寫辣雞類的風氣!!!第一個項目中,成功把Token分離了,本來下一步是分割簡單的語句,然後由簡單語句構造語法樹,但是那樣就把一些關鍵字給剔除了,只能給簡單語句類添加一些關鍵字的標誌,表示這句話有被這個關鍵字修飾!!!
實在進行不下去,休息了半個月,然後畫了一週寫了第二個項目!把加、減、乘、除、乘方、括號構成的算術式子給構造了抽象語法樹!!!雖然功能還不及上次用後綴表達式寫的計算器,但是還是基本搞懂了一些CreateASTree的方法!
文法
設計這樣一個簡單的文法G
1)E ::= E biop E
2)biop ::= +|-|*|/|^
3)E ::= "(" E ")"
4)E ::= Num|Identifier
5)E ::= (+|-) E
假如給出一個式子 : 1 + ( 2 + 3),根據G可以做出下面的推導
E => E + E
=> identifier + ( E )
=> identifier + ( E + E )
=> identifier + ( identifier + identifier )
這樣的推導就是由上而下的,先把整體推導出不同的表達式部分,然後再由各種的表達式向下推導不同的部分或內容!!!但是,從遍歷上來講,我只想遍歷一次,而迭代暫時也不太會寫!!!所以像上面這樣會多次遍歷到一個Token的推導方法,我這次就不考慮了。
我的方案是從頭到尾,一個一個Token來處理,每個Token都只處理一次!(不是隻判斷一次)
1 => + => + => + => + => + => +
/ \ / \ / \ / \ / \ / \
1 NULL 1 ( 1 ( 1 ( 1 ( 1 ()
| | | |
1 + + +
/ \ / \ / \
2 NULL 2 3 2 3
設計解釋器的類
我這個項目用瞭解釋器模式,簡單來講就是一顆類繼承的樹中,除基類外有兩種類——一直是可以有子類,即有孩子節點的,稱爲非終結符——一種是不可以有子類,即不能有孩子節點的,稱爲終結符!
然後通過一個封裝類,操控這顆類繼承的樹,來構造語法樹!
在構造完成後,通過類繼承的樹中都有的interpret() 方法,返回最終語法樹的結果!
設計構造語法樹的指針
除了一些函數內臨時的指針,其他必須的指針如下
- root保存語法樹的根節點,這裏的語法樹就是指總的語法樹,不會保存其他的小樹!
- expTmp保存待處理的節點的父節點,比如上面推導中的None,本身就是空,所以我要保留的就是那個加號節點!——待處理的節點只能是加減乘除、乘方、左括號節點這幾種!
- expTmpLeft保存最底層的沒有匹配到右括號的左括號節點,這個節點就叫做左括號節點!在遍歷全局中綴表達式時,出現了左括號就要創建一個左括號節點,這個指針會保存最新的左括號節點,當出現一個右括號時,這個指針指的左括號節點就把標誌matched_flag置爲true
- v_NoMatchedLLeft保存所有的未匹配的左括號節點,用於expTmpLeft指針的上移——每匹配到一個右括號,對應的左括號節點就被pop出這個容器了!!!(這個容器中的左括號節點,都是在一條鏈上的)
Num|Identifier
1.如果語法樹爲空,直接創建一個Num/Identifeir節點(下面簡寫爲N)
NULL -> N
2.如果語法樹不爲空,那麼肯定有待處理的節點,expTmp肯定不爲空
——1)如果待處理節點是左括號節點,就只有下面一種情況,直接把N接在此左括號節點下即可!
待處理節點還在這個左括號節點上!
( -> (
|
N
——2)如果待處理節點是加減乘除、乘方, 就社設置待處理節點的右孩子,爲新創建的N。
待處理節點由op上移到expTmpLeft的位置——【表示下面的樹完成了,可以考慮匹配右括號了,但是如果接下來還沒匹配到右括號的話,還可以處理下面的子樹。】
op = +|-|*|/|^
?=一棵子樹
op -> op
/ \ / \
? NULL ? Num
加減
1.如果語法樹爲空時,此時出現的+、-可以當做正負號,創建一個N,左孩子默認0,右孩子爲空。
待處理節點設置爲N的位置。
op = +|-
NULL -> op
/ \
0 NULL
2.存在語法樹,還存在待處理的節點
——1)待處理的節點是左括號節點。對左括號節點中的內容進行P1操作!
——2)待處理的節點是加減乘除、乘方、Num。對待處理節點指針expTmp進行P1操作!
3.不存在待處理的節點。對root節點進行P1操作!
P1操作
就是對已有的一棵樹進行左結合操作!然後更新原有的根節點爲op,
待處理節點指針放在op上!
op = +|-
_node = 某個樹的根節點
_node -> op
/ \
_node NULL
乘除
1.如果語法樹爲空,這時出現一個*、/ 肯定有問題,要拋出異常。
2.如果存在語法樹,存在待處理節點
——1)待處理的節點是左括號節點。對左括號節點中的內容進行P2操作!
——2)待處理的節點是加減乘除、乘方、Num。對待處理節點指針expTmp進行P2操作!
3.不存在待處理的節點。對root節點進行P2操作!
P2操作
——1)當小樹的根節點是乘除、乘方、Num類型時,進行左結合操作,
待處理節點指針指向op!
——2)當小樹的根節點是加減類型時,進行右結合操作,
待處理節點指針指向op!
op = *|/
op1 = *|/|^|N
op2 = +|-
? = 一棵子樹
第一種情況(左結合): op1 -> op
/ \ / \
? ? op1 NULL
/ \
? ?
第二種情況(右結合): op2 -> op2
/ \ / \
? ? ? op
/ \
? NULL
乘方
1.如果語法樹爲空,這時出現一個 ^ 肯定有問題,要拋出異常。
2.如果存在語法樹,存在待處理節點
——1)待處理的節點是左括號節點。對左括號節點中的內容進行P3操作!
——2)待處理的節點是加減乘除、乘方、Num。對待處理節點指針expTmp進行P3操作!
3.不存在待處理的節點。對root節點進行P3操作!
P3操作
——1)如果小樹的根節點是左括號或N ,那麼就進行左結合操作!
待處理節點指針指向op!
——2)如果小樹的根節點是其他類型的,那麼判斷一下小樹的根節點的右孩子,如果不是N類型的,就判斷小樹的根節點的右孩子的右孩子,,,,直到找到一個節點,其右孩子是N類型!然後進行右結合操作!
待處理節點指針指向op!
op = ^
op1 = (|N
op2 = +|-|*|/|^
? = 一棵子樹
第一種情況(左結合): op1 -> op
/ \ / \
? ? op1 NULL
/ \
? ?
第二種情況(右結合): op2 -> op2 ....-> op2 -> op2
/ \ / \ / \ / \
? ? ? op2 ? op2 ? op2
/ \ / \ / \
? ? ? . ? .
. .
op2 op2
/ \ / \
? N ? op
/ \
N NULL
左括號
1.如果語法樹爲空,就創建一個左括號節點。expTmpLeft指針指向這個節點,並把這個指針push進v_NoMatchedLLeft容器中!
NULL -> (
2.如果有待處理的節點
——1)待處理節點是左括號節點,用expTmpLeft創建新的左括號節點(記爲L),然後設置待處理的節點的內容爲L。
( -> (
|
(
——2)待處理節點是加減乘除、乘方,用expTmpLeft創建L,然後設置待處理的節點的右孩子爲expTmpLeft。
op = +|-|*|/|^
? = 一棵小樹
op -> op
/ \ / \
? NULL ? (
1)2)操作完了都要把expTmpLeft放入v_NoMatchedLLeft中,讓待處理的節點的指針expTmp指向expTmpLeft
3.沒有待處理的節點,拋出異常。
右括號
1.如果語法樹不存在,拋出異常。
2.沒有待處理的節點,拋出異常。
3.有待處理的節點
——1)待處理節點不是左括號節點,拋出異常
——2)待處理節點是左括號節點,判斷該左括號節點是否已經匹配到了,如果匹配到了就拋出異常,如果沒有匹配到,就置位該左括號節點的標誌爲true匹配到!然後從容器中pop出該節點!!!!將expTmpLeft上移到容器中最後一個左括號節點,並且該左括號節點沒被匹配到!!!如果找到這樣的一個節點,將expTmp指向expTmpLeft,,,,如果找不到就都設置爲空。
項目演示
#include"Expr.h"
/*test.suatin中的內容爲 1+(3-2*4)/1 - 2^(((2)+1)) */
void main() {
sua::file_dir = "C:\\Users\\LX\\Desktop\\test.suatin";
//詞法分析
SHOWFUNCTIME("詞法分析",sua::Lexer);
//顯示全局中綴表達式
SHOWFUNCTIME("全局中綴表達式", sua::ShowGlobalInfix);
//語法分析
sua::Parser p;
SHOWFUNCTIME("語法分析·構造語法樹",p.CreateASTree);
//顯示語法樹
SHOWFUNCTIME("語法分析·顯示語法樹",p.ShowASTree);
//std::cout << "語法樹是否完全==>" << (p.GetCompletedASTreeFlag()==1?"true":"false") << std::endl;
//顯示解釋結果
SHOWFUNCTIME("語法分析·解釋語法樹",p.interpret);
system("pause");
}
詞法分析 time consumed 204 ms
中綴表達式>
name pos type
1 0 7
+ 1 15
( 2 27
3 3 7
- 4 16
2 5 7
* 6 13
4 7 7
) 8 28
/ 9 14
1 10 7
- 11 16
2 12 7
^ 13 12
( 14 27
( 15 27
( 16 27
2 17 7
) 18 28
+ 19 15
1 20 7
) 21 28
) 22 28
全局中綴表達式 time consumed 497 ms
語法分析·構造語法樹 time consumed 1 ms
------------------------------------------------------------
-
├── +
│ ├── 1
│ └── /
│ ├── ()
│ │ └── -
│ │ ├── 3
│ │ └── *
│ │ ├── 2
│ │ └── 4
│ └── 1
└── ^
├── 2
└── ()
└── ()
└── +
├── ()
│ └── 2
└── 1
------------------------------------------------------------
語法分析·顯示語法樹 time consumed 646 ms
result = -12
語法分析·解釋語法樹 time consumed 12 ms
請按任意鍵繼續. . .
參考:
怎麼用C語言畫出二叉樹圖形
項目代碼地址CSDN
https://download.csdn.net/download/weixin_41374099/12178913
項目代碼地址BDWP
鏈接:https://pan.baidu.com/s/14lXkWNohXHIWefuU9p_EJw
提取碼:eiiy
複製這段內容後打開百度網盤手機App,操作更方便哦