lang:自制編程語言2——Concrete Syntax Tree

前言

之前的文章
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,操作更方便哦

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