前端碼農之蛻變 — AST(抽象語法樹)

發表於2018-11-29

前言

首先,先說明下該文章是譯文,原文出自《AST for JavaScript developers》。很少花時間特地翻譯一篇文章,咬文嚼字是件很累的事情,實在是這篇寫的太棒了,所以忍不住想和大家一起分享。

該譯文出自我的部落格:github.com/CodeLittlePrince/blog/issues/19,我的部落格會不定時更新各種型別文章,希望大家支援。

OK,我們直接進入正題。

為什麼要談AST(抽象語法樹)?

如果你檢視目前任何主流的專案中的devDependencies,會發現前些年的不計其數的外掛誕生。我們歸納一下有:javascript轉譯、程式碼壓縮、css前處理器、elint、pretiier,等。有很多js模組我們不會在生產環境用到,但是它們在我們的開發過程中充當著重要的角色。所有的上述工具,不管怎樣,都建立在了AST這個巨人的肩膀上。
image

所有的上述工具,不管怎樣,都建立在了AST這個巨人的肩膀上

我們定一個小目標,從解釋什麼是AST開始,然後到怎麼從一般程式碼開始去構建它。我們將簡單地接觸在AST處理基礎上,一些最流行的使用例子和工具。並且,我計劃談下我的js2flowchart專案,它是一個不錯的利用AST的demo。OK,讓我們開始吧。
image

什麼是AST(抽象語法樹)?

It is a hierarchical program representation that presents source code structure according to the grammar of a programming language, each AST node corresponds to an item of a source code.

image

估計很多同學會和圖中的喵一樣,看完這段官方的定義一臉懵逼。OK,我們來看例子:
image

這很簡化

實際上,正真AST每個節點會有更多的資訊。但是,這是大體思想。從純文純中,我們將得到樹形結構的資料。每個條目和樹中的節點一一對應。

那怎麼從純文字中得到AST呢?哇哦,我們知道當下的編譯器都做了這件事前。那我們就看看一般的編譯器怎麼做的就可以了。
image

想做一款編譯器是個比較消耗髮量的事情,但幸運的是,我們無需貫穿編譯器的所有知識點,最後將高階語言轉譯為二進位制程式碼。我們只需要關注詞法分析和預發分析。這兩步是從程式碼中生成AST的關鍵所在。

第一步,詞法分析,也叫做掃描scanner。它讀取我們的程式碼,然後把它們按照預定的規則合併成一個個的標識tokens。同時,它會移除空白符,註釋,等。最後,整個程式碼將被分割進一個tokens列表(或者說一維陣列)。
image

當詞法分析原始碼的時候,它會一個一個字母地讀取程式碼,所以很形象地稱之為掃描-scans;當它遇到空格,操作符,或者特殊符號的時候,它會認為一個話已經完成了。

第二步,語法分析,也解析器。它會將詞法分析出來的陣列轉化成樹形的表達形式。同時,驗證語法,語法如果有錯的話,丟擲語法錯誤。
image

當生成樹的時候,解析器會刪除一些沒必要的標識tokens(比如不完整的括號),因此AST不是100%與原始碼匹配的,但是已經能讓我們知道如何處理了。說個題外話,解析器100%覆蓋所有程式碼結構生成樹叫做CST(具體語法樹)

image

我們最終得到的

想要學習更多關於編譯器的知識?
the-super-tiny-compiler,一個賊好的專案。大概200來行程式碼,幾乎每行都有註釋。
image

想要自己建立門程式語言?
LangSandbox,一個更好的專案。它演示瞭如何創造一門程式語言。當然,設計程式語言這樣的書市面上也一坨坨。所以,這專案更加深入,與the-super-tiny-compiler的專案將Lisp轉為C語言不同,這個專案你可以寫一個你自己的語言,並且將它編譯成C語言或者機器語言,最後執行它。

我能直接用三方庫來生成AST嗎?
當然可以!有一坨坨的三方庫可以用。你可以訪問astexplorer,然後挑你喜歡的庫。astexplorer是一個很棒的網站,你可以線上玩轉AST,而且除了js,還有很多其它語言的AST庫。
image

我不得不強調一款我覺得很棒的三方庫,叫做babylon。
image

它被用在大名鼎鼎的babel中,也許這也是它之所以這麼火的原因。因為有babel專案的支援,我們可以意料到它將與時俱進,一直支援最新的JS特性,我們可以放心大膽地用,不怕以後JS又出新版導致程式碼的大規模重構。另外,它的API也非常的簡單,容易使用。

Ok,現在你知道怎麼將程式碼生成AST了,讓我們繼續,來看看現實中的用例。

第一個用例,我想談談程式碼轉化,沒錯,就是那個貨,babel。

Babel is not a ‘tool for having ES6 support’. Well, it is, but it is far not only what it is about.

經常把beble和支援es6/7/8聯絡起來,實際上,這也是我們經常用它的原因。但是,它僅僅是一組外掛中的一個。我們也可以使用它來壓縮程式碼,react相關預發轉譯(如jsx),flow外掛等。
image

babel是一個javascript編譯器。巨集觀來說,它分3個階段執行程式碼:解析(parsing),轉譯(transforming),生成(generation)。我們可以給babel 一些javascript程式碼,它修改程式碼然後生成新的程式碼返回。那它是怎樣修改程式碼的呢?沒錯!它建立了AST,遍歷樹,修改tokens,最後從AST中生成新的程式碼。

我們來從下面的demo中看下這個過程:
image

像我之前提到的,babel使用babylon,所以,首先,我們解析程式碼成AST,然後遍歷AST,再反轉所有的變數名,最後生成程式碼。完成!正如我們看到的,第一步(解析)和第三步(生成)看起來非常常規,我們每次都會做這兩步。所以,babel接管處理了它倆。最後,我們最為關心的,那就是AST轉譯這一步了。

當我們開發babel-plugin的時候,我們只需要描述轉化你AST的節點“visitors”就可以了。
image

將它加入你的babel外掛列表中,設定你webpack的babel-loader配置或者.babelrc中的plugins即可

You may check out Babel-handbook if you would like to learn more about how to build your first babel-plugin.
如果你想要學習怎麼建立你的第一個babel-plugin,可以檢視Babel-handbook
image

讓我們繼續,下一個用例,我想提到的是自動程式碼重構工具,以及神器JSCodeshift。

比如說你想要替換掉所有的老掉牙的匿名函式,把他們變成Lambda表示式(箭頭函式)。
image

你的程式碼編輯器很可能沒法這麼做,因為這並不是簡單地查詢替換操作。這時候jscodeshift就登場了。

如果你聽過jscodeshift,你很可能也聽過codemods,一開始挺這兩個詞可能很困惑,不過沒關係,接下來就解釋。jscodeshift是一個跑codemods的工具。codemod是一段描述AST要轉化成什麼樣的程式碼,這思想和babel的外掛如出一轍。
image

所以,如果你想建立自動把你的程式碼從舊的框架遷移到新的框架,這就是一種很乃思的方式。舉個例子,react 16的prop-types重構。
image

有很多不同的codemodes已經建立了,你可以儲存你需要的,以免手動的修改一坨坨程式碼,拿去揮霍吧:
https://github.com/facebook/jscodeshift
https://github.com/reactjs/react-codemod
image

最後一個用例,我想要提到Prettier,因為可能每個碼農都在日常工作中用到它。
image

Prettier 格式化我們的程式碼。它調整長句,整理空格,括號等。所以它將程式碼作為輸入,修改後的程式碼作為輸出。聽起來很熟悉是嗎?當然!
image

思路還是一樣。首先,將程式碼生成AST。之後依然是處理AST,最後生成程式碼。但是,中間過程其實並不像它看起來那麼簡單。

同樣,如果你想學習更多在美化列印背後理論,這裡有一本你可以深入的書 《A prettier printer》
image

文章迎來尾聲,我們繼續,今天最後一件事,我想提及的就是我的庫,叫做js2flowchart(4.5 k stars 在 Github)。
image

顧名思義,它將js程式碼轉化生成svg流程圖

這是一個很好的例子,因為它向你展現了你,當你擁有AST時,可以做任何你想要做的事。把AST轉回成字串程式碼並不是必要的,你可以通過它畫一個流程圖,或者其它你想要的東西。

js2flowchart使用場景是什麼呢?通過流程圖,你可以解釋你的程式碼,或者給你程式碼寫文件;通過視覺化的解釋學習其他人的程式碼;通過簡單的js語法,為每個處理過程簡單的描述建立流程圖。

馬上用最簡單的方式嘗試一下吧,去線上編輯看看 js-code-to-svg-flowchart

你也可以在程式碼中使用它,或者通過CLI,你只需要指向你想生成SVG的檔案就行。而且,還有VS Code外掛(連結在專案readme中)

那麼,它還能做什麼呢?哇哦,我這裡就不廢話了,大家有興趣直接看這個專案的文件吧。

OK,那它是如何工作的呢?
image

首先,解析程式碼成AST,然後,我們遍歷AST並且生成另一顆樹,我稱之為工作流樹。它刪除很多不重要的額tokens,但是將關鍵塊放在一起,如函式、迴圈、條件等。再之後,我們遍歷工作流樹並且建立形狀樹。每個形狀樹的節點包含視覺化型別、位置、在樹中的連線等資訊。最後一步,我們遍歷所有的形狀,生成對應的SVG,合併所有的SVG到一個檔案中。

結尾

尋找和篩選資料著實辛苦,希望同學們可以多多支援!

相關文章