1. 程式人生 > >語法樹

語法樹

樹狀表 其中 而已 operator href %20 style state col

何為語法樹

什麽是語法樹?

你是否曾想過,這個世界存在這麽多語言的意義。

假如現在你面前有一個物體,它是一個不規則的圓體,整個身體通紅,頭部還有一根細長稍微彎曲偏右呈棕色的圓柱體。
在中文我們稱之為「蘋果」,
在英文我們稱之為「Apple」,
在日文中我們稱之為「アップル」,
在法語中我們稱之為「pomme」,
在德語中我們稱之為「Apfel」,
無論用不同的語言,針對這個物體在文字上、發音上都完全不一樣,但這個物體確確實實的存在這個時空上,顏色、氣味、形狀都不曾因為語言而改變過。

無論這個世界存在多少語言,它們所描述的真理都不曾改變過。

或者說,真理就存在那裏,可以用不同的語言的不同表達方式描述出來。那麽計算機的世界,這麽多編程的語言,C、C++、Java、C#、JavaScript、Python、Go、Ruby等等等,它們共同所描述的真理是什麽?

我們知道人類語言上,無論什麽語種,都會有「主語」「動詞」「賓語」「標點符號」來描述一個現實世界所發生的事件。
而在計算機編程語言上,無論什麽語種,都會有「類型」「運算符」「流程語句」「函數」「對象」等概念來表達計算機中存在內存中的0和1,以及背後運算與邏輯。

語法樹,計算機描述世界真理的樹狀結構。

不同的語言,都會配之不同的語法分析器,而語法分析器是把源代碼作為字符串讀入、解析,並建立語法樹的程序。語法的設計和語法分析器的實現是決定語言外在表現的重要因素。
什麽是語法樹?摘自Wiki一段:

在計算機科學中,抽象語法樹(abstract syntax tree 或者縮寫為 AST),或者語法樹(syntax tree),是源代碼的抽象語法結構的樹狀表現形式,這裏特指編程語言的源代碼。樹上的每個節點都表示源代碼中的一種結構。之所以說語法是「抽象」的,是因為這裏的語法並不會表示出真實語法中出現的每個細節。

一則簡單的例子

如果我們需要讓計算機幫忙算一下 「1加2再乘以3」 的結果,該怎麽表達呢?
現在我們大多數的現代編程語言,都是使用「中綴表達式」的方式來編寫運算,比如JavaScript:

(1 + 2) * 3

而FORTH語言則使用「後綴表達式」,這基本上與日語中的語序是一致的:

1 2 + 3 *

LISP語言使用的「前綴表達式」:

( * (+ 1 2) 3)

我們再看一下這三種表達式的語法樹:

技術分享
表達式語法樹比較.png

可以看出,對於這三種簡單的語言,它們只是在這個語法樹上按不同的規則遍歷而已。三者的代碼看起來差別很大,但實際上所用的樹結構是相同的。

先來看看Python的語法樹

通過Python語言自帶的庫文件ast,我們可以查看特定的代碼被轉換成怎樣的語法樹。

>>> import ast
>>> ast.dump(ast.parse("(1 + 2) * 3"))
Module(
    body=[
        Expr(
            value=BinOp(
                left=BinOp(
                    left=Num(n=1), 
                    op=Add(), 
                    right=Num(n=2)
                ), 
                op=Mult(), 
                right=Num(n=3)
            )
        )
    ]
)

BinOp op = Mult()表示乘法運算,與*相對應;
BinOp op = Add()表示加法運算,與+相對應;
Num n = 1既為數值1。

技術分享
Python語法樹.png

再窺視一下JavaScript的語法樹

在語法復雜的語言中,語法樹是包含很多細節的語法結果表達式,我們需要靠語法樹把這種形式以更簡潔的形式表達出來。

Javascript 有不少工具可以把代碼構造出清晰的語法樹,比如esprima、v8、SpiderMonkey、UglifyJS、AST explorer等。

這裏,我使用「esprima」來探討一下JavaScript運算(1 + 2) * 3的語法樹。

javascript code:

(1 + 2)* 3;

ast for json:

{
    "type": "Program",
    "body": [
        {
            "type": "ExpressionStatement",
            "expression": {
                "type": "BinaryExpression",
                "operator": "*",
                "left": {
                    "type": "BinaryExpression",
                    "operator": "+",
                    "left": {
                        "type": "Literal",
                        "value": 1,
                        "raw": "1"
                    },
                    "right": {
                        "type": "Literal",
                        "value": 2,
                        "raw": "2"
                    }
                },
                "right": {
                    "type": "Literal",
                    "value": 3,
                    "raw": "3"
                }
            }
        }
    ],
    "sourceType": "script"
}

body表示程序體,而程序體中包含了一則表達式ExpressionStatement, 表達式體裏包含了操作符 *,以及左右兩邊表達式,其中右邊是數字3,而左邊表達式還包含一層表達式,裏面是一個+ 操作符,以及左右兩邊分別為12的數字。

技術分享
javascript語法樹.png

如果還沒有看懂,你可以到這裏看一下這段代碼所生成的語法樹:AST for (1 + 2)* 3;

我們可以利用語法樹做些什麽?

看到這裏你可能會問,知道語法是又有什麽用呢?跟我日常編寫代碼貌似半毛錢關系都沒有。其實語法樹還是很有用的,想一下如果想做「語法高亮」、「關鍵字匹配」、「作用域判斷」、「語言轉換」以及「代碼壓縮」等等,都是最好把代碼解構成語法樹之後再去各種操作,當然僅僅解構還不夠,還需要提供各種函數去遍歷與修改語法樹。

另一方面,去研究、去探討計算機真實的世界不是一個很精彩很刺激的過程麽?

語法樹