轉(zhuǎn)載:https://github.com/CodeLittlePrince/blog/issues/19
前言
首先空镜,先說明下該文章是譯文蔫慧,原文出自《AST for JavaScript developers》躏哩。很少花時間特地翻譯一篇文章,咬文嚼字是件很累的事情,實在是這篇寫的太棒了迎卤,所以忍不住想和大家一起分享贾虽。
OK定罢,我們直接進入正題。
為什么要談AST(抽象語法樹)?
如果你查看目前任何主流的項目中的devDependencies
壳贪,會發(fā)現(xiàn)前些年的不計其數(shù)的插件誕生。我們歸納一下有:javascript轉(zhuǎn)譯寝杖、代碼壓縮违施、css預(yù)處理器、elint瑟幕、pretiier磕蒲,等。有很多js模塊我們不會在生產(chǎn)環(huán)境用到只盹,但是它們在我們的開發(fā)過程中充當(dāng)著重要的角色辣往。所有的上述工具,不管怎樣殖卑,都建立在了AST這個巨人的肩膀上站削。
所有的上述工具,不管怎樣孵稽,都建立在了AST這個巨人的肩膀上
我們定一個小目標(biāo)许起,從解釋什么是AST開始,然后到怎么從一般代碼開始去構(gòu)建它肛冶。我們將簡單地接觸在AST處理基礎(chǔ)上街氢,一些最流行的使用例子和工具。并且睦袖,我計劃談下我的js2flowchart項目珊肃,它是一個不錯的利用AST的demo。OK馅笙,讓我們開始吧伦乔。
什么是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.
估計很多同學(xué)會和圖中的喵一樣董习,看完這段官方的定義一臉懵逼烈和。OK,我們來看例子:
這很簡化
實際上皿淋,正真AST每個節(jié)點會有更多的信息招刹。但是恬试,這是大體思想。從純文本中疯暑,我們將得到樹形結(jié)構(gòu)的數(shù)據(jù)训柴。每個條目和樹中的節(jié)點一一對應(yīng)。
那怎么從純文本中得到AST呢妇拯?哇哦幻馁,我們知道當(dāng)下的編譯器都做了這件事情。那我們就看看一般的編譯器怎么做的就可以了越锈。
想做一款編譯器是個比較消耗發(fā)量的事情仗嗦,但幸運的是,我們無需貫穿編譯器的所有知識點甘凭,最后將高級語言轉(zhuǎn)譯為二進制代碼稀拐。我們只需要關(guān)注詞法分析和語法分析。這兩步是從代碼中生成AST的關(guān)鍵所在对蒲。
第一步钩蚊,詞法分析,也叫做掃描scanner蹈矮。它讀取我們的代碼,然后把它們按照預(yù)定的規(guī)則合并成一個個的標(biāo)識tokens鸣驱。同時泛鸟,它會移除空白符,注釋踊东,等北滥。最后,整個代碼將被分割進一個tokens列表(或者說一維數(shù)組)闸翅。
當(dāng)詞法分析源代碼的時候再芋,它會一個一個字母地讀取代碼,所以很形象地稱之為掃描-scans坚冀;當(dāng)它遇到空格济赎,操作符,或者特殊符號的時候记某,它會認為一個話已經(jīng)完成了司训。
第二步,語法分析液南,也解析器壳猜。它會將詞法分析出來的數(shù)組轉(zhuǎn)化成樹形的表達形式。同時滑凉,驗證語法统扳,語法如果有錯的話喘帚,拋出語法錯誤。
當(dāng)生成樹的時候咒钟,解析器會刪除一些沒必要的標(biāo)識tokens(比如不完整的括號)啥辨,因此AST不是100%與源碼匹配的,但是已經(jīng)能讓我們知道如何處理了盯腌。說個題外話溉知,解析器100%覆蓋所有代碼結(jié)構(gòu)生成樹叫做CST(具體語法樹)
我們最終得到的
想要學(xué)習(xí)更多關(guān)于編譯器的知識?
the-super-tiny-compiler腕够,一個賊好的項目级乍。大概200來行代碼,幾乎每行都有注釋帚湘。
想要自己創(chuàng)建門編程語言玫荣?
LangSandbox,一個更好的項目大诸。它演示了如何創(chuàng)造一門編程語言捅厂。當(dāng)然,設(shè)計編程語言這樣的書市面上也一坨坨资柔。所以焙贷,這項目上一個相比更加深入,與the-super-tiny-compiler的項目將Lisp轉(zhuǎn)為C語言不同贿堰,這個項目你可以寫一個你自己的語言辙芍,并且將它編譯成C語言或者機器語言,最后運行它羹与。
我能直接用三方庫來生成AST嗎故硅?
當(dāng)然可以!有一坨坨的三方庫可以用纵搁。你可以訪問astexplorer吃衅,然后挑你喜歡的庫。astexplorer是一個很棒的網(wǎng)站腾誉,你可以在線玩轉(zhuǎn)AST徘层,而且除了js,還有很多其它語言的AST庫妄辩。
我不得不強調(diào)一款我覺得很棒的三方庫惑灵,叫做babylon。
它被用在大名鼎鼎的babel中眼耀,也許這也是它之所以這么火的原因英支。因為有babel項目的支持,我們可以意料到它將與時俱進哮伟,一直支持最新的JS特性干花,因此可以放心大膽地用妄帘,不怕以后JS又出新版導(dǎo)致代碼的大規(guī)模重構(gòu)。另外池凄,它的API也非常的簡單抡驼,容易使用。
Ok肿仑,現(xiàn)在你知道怎么將代碼生成AST了致盟,讓我們繼續(xù),來看看現(xiàn)實中的用例尤慰。
第一個用例馏锡,我想談?wù)劥a轉(zhuǎn)化,沒錯伟端,就是那個貨杯道,babel。
Babel is not a ‘tool for having ES6 support’. Well, it is, but it is far not only what it is about.
經(jīng)常把beble和支持es6/7/8聯(lián)系起來责蝠,實際上党巾,這也是我們經(jīng)常用它的原因。但是霜医,它僅僅是一組插件中的一個齿拂。我們也可以使用它來壓縮代碼,react相關(guān)語法轉(zhuǎn)譯(如jsx)支子,flow插件等创肥。
babel是一個javascript編譯器。宏觀來說值朋,它分3個階段運行代碼:解析(parsing),轉(zhuǎn)譯(transforming)巩搏,生成(generation)昨登。我們可以給babel 一些javascript代碼,它修改代碼然后生成新的代碼返回贯底。那它是怎樣修改代碼的呢丰辣?沒錯!它創(chuàng)建了AST禽捆,遍歷樹笙什,修改tokens,最后從AST中生成新的代碼胚想。
像我之前提到的琐凭,babel使用babylon,所以浊服,首先统屈,我們解析代碼成AST胚吁,然后遍歷AST,再反轉(zhuǎn)所有的變量名愁憔,最后生成代碼腕扶。完成!正如我們看到的吨掌,第一步(解析)和第三步(生成)看起來很常規(guī)半抱,我們每次都會做這兩步。所以膜宋,babel接管處理了它倆窿侈。最后,我們最為關(guān)心的激蹲,那就是AST轉(zhuǎn)譯這一步了棉磨。
當(dāng)我們開發(fā)babel-plugin的時候,我們只需要描述轉(zhuǎn)化你AST的節(jié)點“visitors”就可以了学辱。
將它加入你的babel插件列表中乘瓤,設(shè)置你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.
如果你想要學(xué)習(xí)怎么創(chuàng)建你的第一個babel-plugin,可以查看Babel-handbook
讓我們繼續(xù)策泣,下一個用例衙傀,我想提到的是自動代碼重構(gòu)工具,以及神器JSCodeshift萨咕。
比如說你想要替換掉所有的老掉牙的匿名函數(shù)统抬,把他們變成Lambda表達式(箭頭函數(shù))。
你的代碼編輯器很可能沒法這么做危队,因為這并不是簡單地查找替換操作聪建。這時候jscodeshift就登場了。
如果你聽過jscodeshift
茫陆,你很可能也聽過codemods
金麸,一開始挺這兩個詞可能很困惑,不過沒關(guān)系簿盅,接下來就解釋挥下。jscodeshift是一個跑codemods
的工具。codemod
是一段描述AST要轉(zhuǎn)化成什么樣的代碼桨醋,這思想和babel的插件如出一轍棚瘟。
所以,如果你想創(chuàng)建自動把你的代碼從舊的框架遷移到新的框架喜最,這就是一種很乃思的方式偎蘸。舉個例子,react 16的prop-types重構(gòu)。
有很多不同的codemodes
已經(jīng)創(chuàng)建了禀苦,你可以保存你需要的蔓肯,以免手動的修改一坨坨代碼,拿去揮霍吧:
https://github.com/facebook/jscodeshift
https://github.com/reactjs/react-codemod
最后一個用例振乏,我想要提到Prettier蔗包,因為可能每個碼農(nóng)都在日常工作中用到它。
Prettier 格式化我們的代碼慧邮。它調(diào)整長句调限,整理空格,括號等误澳。所以它將代碼作為輸入耻矮,修改后的代碼作為輸出。聽起來很熟悉是嗎忆谓?當(dāng)然裆装!
思路還是一樣。首先倡缠,將代碼生成AST哨免。之后依然是處理AST,最后生成代碼昙沦。但是琢唾,中間過程其實并不像它看起來那么簡單。
同樣盾饮,如果你想學(xué)習(xí)更多在美化打印背后理論采桃,這里有一本你可以深入的書 《A prettier printer》。
文章迎來尾聲丘损,我們繼續(xù)普办,今天最后一件事,我想提及的就是我的庫徘钥,叫做js2flowchart
(4.5 k stars 在 Github)泌豆。
顧名思義,它將js代碼轉(zhuǎn)化生成svg流程圖
這是一個很好的例子吏饿,因為它向你展現(xiàn)了你,當(dāng)你擁有AST時蔬浙,可以做任何你想要做的事猪落。把AST轉(zhuǎn)回成字符串代碼并不是必要的,你可以通過它畫一個流程圖畴博,或者其它你想要的東西笨忌。
js2flowchart使用場景是什么呢?通過流程圖俱病,你可以解釋你的代碼官疲,或者給你代碼寫文檔袱结;通過可視化的解釋學(xué)習(xí)其他人的代碼;通過簡單的js語法途凫,為每個處理過程簡單的描述創(chuàng)建流程圖垢夹。
馬上用最簡單的方式嘗試一下吧,去線上編輯看看 js-code-to-svg-flowchart
你也可以在代碼中使用它维费,或者通過CLI果元,你只需要指向你想生成SVG的文件就行。而且犀盟,還有VS Code插件(鏈接在項目readme中)
那么而晒,它還能做什么呢?哇哦阅畴,我這里就不廢話了倡怎,大家有興趣直接看這個項目的文檔吧。
首先,解析代碼成AST冯事,然后焦匈,我們遍歷AST并且生成另一顆樹,我稱之為工作流樹昵仅。它刪除很多不重要的額tokens缓熟,但是將關(guān)鍵塊放在一起,如函數(shù)摔笤、循環(huán)够滑、條件等。再之后吕世,我們遍歷工作流樹并且創(chuàng)建形狀樹彰触。每個形狀樹的節(jié)點包含可視化類型、位置命辖、在樹中的連接等信息况毅。最后一步,我們遍歷所有的形狀尔艇,生成對應(yīng)的SVG尔许,合并所有的SVG到一個文件中。
結(jié)尾
尋找和篩選資料著實辛苦终娃,希望同學(xué)們可以多多支持味廊!