AST抽象語法樹的基本思想

AST抽象語法樹的基本思想

前言

    在閱讀java ORM框架spring data jpa的源碼時,發現Hibernate(spring data jpa依賴Hibernate核心代碼)在底層使用了AST抽象語法樹,將hql轉換爲sql,這激發了我研究AST的興趣。

AST概述

    AST(Abstract Syntax Tree)抽象語法樹多用作編程語言的分析和轉換,C語言編譯器將c源碼轉換爲彙編,java編譯器將java代碼轉換爲java字節碼,還有一些比較高級的用法,比如同種語言代碼的優化、不同種語言代碼的相互轉化等。

    抽象語法樹從術語定義上就能看出,本身是一種樹狀的數據結構,“1 + 2”使用抽象語法樹可以表示爲:

AST抽象語法樹的基本思想-1_2.png

這樣做的目的是,將原始語句分解成了單個的語法單元,同時保留了語法單元之間的層次結構,後續通過對語法單元的改造或者替換,重新按照某種規則遍歷,即可完成原始語句到目標語句的轉換。比如,以“1 + 2”爲例,可以將“+”替換爲add,1和2理解爲add函數的參數,即可實現原始運算語句到函數調用的轉換。

AST抽象語法樹的基本思想-add.png

    AST抽象語法樹的使用,整體上可以分爲三步:

  • 解析:將原始語句解析爲抽象語法樹
  • 轉換:操作抽象語法樹節點完成轉換
  • 生成:根據轉換後的抽象語法樹生成目標語句

其中,最重要的是需要理解抽象語法樹的結構,這是解析和生成抽象語法樹的基礎。

AST結構

    個人感覺,理解抽象語法樹結構的最佳例子是xml格式文本,這兩種形式的對比能夠充分顯示抽象語法樹結構是爲了表示空間或者時間或者邏輯關係上的層次。

<city>
    <park>ZhongShan</park>
    <people>
        <id>123456</id>
        <name>"Tom"</name>
        <son>
            <id>123457</id>
            <name>"John"</name>
        </son>
    </people>
</city>

    上面構造的xml是用來表示,有個城市中一個公園叫ZhongShan,這個城市住着一個人,他的id是123456,姓名叫Tom。Tom有一個兒子,id是123457,名叫John。這種嵌套的關係用AST表示,如圖:

AST抽象語法樹的基本思想-xml.png

    xml到AST圖的轉換,實際上是空間層次的對應關係。可能大家還是會對邏輯層次關係如果轉換成AST有疑問,所以這裏再以表達式“5-2*(3-1)+4”爲例來進一步分析這種轉換。

AST抽象語法樹的基本思想-expr.png 如圖爲“5-2*(3-1)+4”的抽象語法樹,其實就是中綴表達式的樹形表示。這裏構造的基本邏輯是,越是排在後面的運算離根節點越近。括號中的表達式“3-1”最先被運算,因此位於最底層。而“+ 4”運算最後執行,所以這裏的“+”位於根節點。表達式的AST圖和xml的AST圖不同之處在於,這裏同層還存在順序關係,左邊的節點優先級要高於右邊節點的優先級。

    通過上述兩個例子的演示,這裏再介紹代碼的抽象語法樹似乎就容易理解多了。

while(b > 0)
{
    if(a < b)
        a = b-a;
    else
        b = a-b;
}
return a;

在代碼的構造抽象語法樹之前,首先需要明白,這些語句之間存在執行上的順序關係,也存在不同層級下的嵌套關係。比如上述代碼,while循環語句塊由大括號包裹,和return語句處於一個層級,但while語句塊先會被執行。而while語句塊中又包含了循環判斷“b != 0”和if的判斷語句塊,這屬於while下語句的嵌套。其次,語句結構也更加複雜,比如上例中的語句除了表達式、賦值語句之外,還有while、if和對應的判斷條件,所以會拆分出更多的語法單元。

AST抽象語法樹的基本思想-s.png

    statement_list語法單元,用來表示子節點都是並列依次執行的語句。while子樹中包含了while循環的條件判斷和if的語句塊,if語句塊包含了if的條件判斷和兩個賦值語句。在編譯器構造同樣類型語法樹時,一般會使用與具體語言無關的語法單元的命名,這樣在後續轉換的轉換隻是對節點採用不同的翻譯模式而已,做到了和具體語言的解耦。

AST解析

    使用AST對原始語句解析時,需要先進行詞法分析。
    詞法分析會根據既定的語法單元表,將原始語句分割成一維數組語法單元列表(token表)。語法單元表根據場景不同,如上述三個例子,可以自行定義。一般而言,詞法分析時會將連續的空格當做分割符,自動切分語法單元。

    獲得token表之後,再使用語法分析,將一維無結構的token錶轉化爲樹形結構。在語法分析時,也會驗證語法的正確性。如果出現不符合語法的語句,就會拋出錯誤,編譯報錯一般就是這個階段的產物。

轉換

    根據目的的不同,AST轉換沒有一套固定的標準,有時候只是對匹配節點簡單的替換,有時候可能是對匹配子樹結構的調整或者替換。一般這個過程包含遍歷和轉換兩步。

    抽象語法樹可以使用一般樹的遍歷方法。如果忘記了,可以溫習一下先序遍歷、中序遍歷和後序遍歷。目前使用比較多的antlr中,使用了先序遍歷和後序遍歷。

生成

    生成是AST解析的逆向工程,生成過程中也需要用到樹的遍歷。雖然有時候生成和轉換可能糅合在一起同時進行,但是生成邏輯比單純進行轉換時要複雜。

    在生成的遍歷過程中,需要對所有不同類型語法單元的節點的所有情況定義不同的處理邏輯,而不同類型語法節點下子樹的遍歷順序也有可能會不同。所以需要爲所有情況進行枚舉。

AST抽象語法樹的基本思想-生成.png

    這種情況的感性認知,可以以上述例子中代碼語法抽象樹爲例。在statement_list節點下,子樹代表的語句塊需要按順序並列,所以在先遍歷完while語句塊子樹之後,回到statement_list節點,再到return子樹下,生成return語句需要另起一行。而while節點下需要先考慮條件語句,在不回車換行的基礎上添加括號,完成while( b > 0 )語句的構造。同理while節點下while體中需要自動添加{}…使用抽象語法樹的生成邏輯需要事無鉅細的列出可能遇到的所有情況和處理方式。

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