1. 程式人生 > 其它 >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”使用抽象語法樹可以表示為:

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

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

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

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

AST結構

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

<city>
    <park>ZhongShan</park>
    <people>
        <id>123456</id>
        <name>"Tom"</name>
        <son>
            <id>123457</id>
            <name>"John"</name>
        </son>
    </people>
</city>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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

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

如圖為“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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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

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

AST解析

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

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

轉換

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

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

生成

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

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

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

 

原文: https://www.freesion.com/article/5042948081/