轉自:https://zhuanlan.zhihu.com/p/36301857前端
最近在個人 timline 上面出現了不少相似《前端爲何要學編譯原理》這類文章以及《前端怎麼學AST》這類的問題,可是卻發現並無人給你們介紹前端要如何以系統而且正確地學習編譯原理,因此我就結合本身的經驗以及走過的彎路來給你們分享點心得和經驗,但願能讓你們少走點彎路。webpack
最後我並非前端,只是剛好會寫點 JavaScript 而已。git
上篇:程序員
下篇:github
你們提起編譯原理第一反應都是很難,難到無從下手,可是爲何難呢?說白了,編譯原理不就是研究把一門語言解析而且轉換成另外一門語言的技術嗎?這項技術到底有哪些地方成爲了阻礙呢?我認爲這個最大的阻礙其實就是「編程語言」自己。web
我相信在看這篇文章的朋友至少已經學會了 JavaScript 了吧,可是我想多嘴問一句,你們真的懂 JavaScript 嗎?能描述出 JavaScript 的語法規則嗎?能理解語法所代指的邏輯結構嗎?知道 JavaScript 是如何在被解釋和執行的嗎?因此,你們真的懂 JavaScript 嗎?反正我是至今沒有底氣說本身」精通「 JavaScript ,緣由是我還不懂如何實現一個 JIT。正則表達式
咱們多數時候稱本身「精通」某編程語言的時候,僅僅指會熟練使用某編程語言,可是編譯原理這門學科折騰的核心偏偏是編程語言,它要求咱們對編程語言有深刻的瞭解,瞭解它是如何構造和解釋的。咱們若是沒有這項基礎實際上是很難學好這門學科的。算法
推薦閱讀:編程
部分國外高校的計算機專業喜歡用 Lisp 系的 Scheme 入門,一開始我並不明白其中原因,直到我發現他們的課程做業中最後總會要求實現一個簡易的 Lisp 解釋器時我才恍然大悟。外國學校安排課程的水平真是高明,學校教 Scheme 可不是爲了讓學生拿來寫工程代碼,而是讓學生學習編程以及編程語言自己究竟是一個什麼東西。小程序
Lisp 是一門具有現代編程語言特性的幾乎最簡的實現,全部編程語言都是 Lisp 方言真的不只僅是一句玩笑話。簡易的 Lisp 的解釋難度很低,Lisp 語法的解析只有解析 JSON 同等的難度,咱們會常常看到不少新手用百來行代碼就能實現一個 Lisp 解釋器。雖然實現一個 Lisp 解釋器不難,可是他對學生來講的意義很是重大,它能讓學生們對編程語言和程序的構造和執行有一個很是很是基礎但又很是全面的認識。而這種對編程語言全面的認識,也正是咱們這些拿着 C/C艹 亦或者 JavaScript 入門的你們所缺失的。
因此如何學好編程語言?正途固然是啃咱們的經典神書 《SICP》了,不過考慮到 《SICP》嚴重的教科書屬性,講地並不生動有趣,因此仍是給你們推薦一個科普性質更強的書,叫作《計算的本質》你們能夠用這本書先入門,若是學有餘力或者很是有興趣再去啃《SICP》。
推薦閱讀:
上一節咱們提過,Lisp 的解析難度和 JSON 是同樣的,那咱們能不能乾脆用 JSON 代替代碼呢?固然能夠,JavaScript 的解析後的語法樹就是用 JSON 表示的。因此就表達能力來講,JavaScript 的代碼和 JSON 是沒有差異的。那麼問題來了,代碼究竟是什麼?
其實代碼跟 JSON 同樣,是一種結構化的文本數據格式。在這裏咱們要僅僅抓着兩個特色——「文本」和「結構化」。
代碼的第一個特色是文本,那意味着咱們全部對字符串的拼接、截取或者替換等全部操做,均可以應用在代碼上面。不少程序員雖然都能對各種文本的讀寫了如指掌,但你們好像都沒有意識到代碼文件,也能夠是那個能夠讀寫、修改的文件之一。
對代碼文件的讀寫和操做是進入編譯世界的第一個重要門檻,有的時候並不須要太複雜的算法就可以對代碼作一些有意義的轉換,好比咱們能夠直接經過正則分析 import / export / require 來實現一個簡易的 webpack,好比在我以前一篇文章也是經過簡單的正則優化尾遞歸代碼。真正有意識地把代碼文件當成文本文件之後,咱們就能把代碼今後拉下「神壇」,可讓你們可以像思考文本同樣思考代碼。
代碼的第二個特色則是結構化。不知道你們能不能理解,代碼裏面除了字面量意外,其餘部分都只是標識結構而並不具備實際意義,賦予這些結構意義是解釋器如何和執行這段代碼。這個特色就是要求咱們在看待代碼的時候,要在腦中造成一種結構,而再也不是一行一行的字符串。
var a = 123 // 除了字面量 123 外其餘全部字符都是標識結構
好比上面這串簡單的 JavaScript 代碼,var 這是一個抽象符號,他是 var 也好是 val 也好,就算是 #%$ 都沒有任何問題,惟一的目的就是標識了這個結構(語句)是一個聲明賦值。變量名 a 標識的是一種聯繫,這個 a 具體是什麼也是可有可無的,只要它所標識的聯繫不變,a 也是能夠替換成任何字符。這裏面惟一有實際意義的就是那個 123,我不能把它換成 456。
知乎以前有一個問題問爲何一些大佬可以在兩個星期內學會一門編程語言,個人回答是兩個星期都夠咱們造一門編程語言了,就像 JavaScript 也就是 布蘭登·艾克 大佬花了一個星期設計的。我雖然確定不及這些大佬們,可是讓我兩個星期內拿 C艹 造一個 JavaScript 1.0 仍是沒什麼太大問題的。因此只要把文章到這裏以前推薦的書好好看了,基礎補上了,那麼其實你們每一個人都能輕鬆在兩個星期內學好一門編程語言。
最後仍是要提一下,可以用兩個星期學好一門編程語言並不表明能用兩個星期學好一個領域。就像你不能說你學會了 JavaScript 就等於學會了前端,也不能說學會了 Python 就等於學會了人工智能(雖然如今不少坑爹培訓班打着人工智能旗號教 Python 基礎),編程語言僅僅是編程語言,僅僅是一個工具。
推薦閱讀:
推薦工具:
這篇文章到這裏已是第四個小節了,但直到這裏纔算可以正式抱起咱們的經典教材——龍書、虎書或者鯨魚書進行學習了。這一節簡單介紹一下編譯器前端技術 —— Parser。
編譯器前段就在幹一件事,把代碼這個結構化的文本文件解析成咱們計算機能夠理解的數據結構 —— 抽象語法樹(AST)。解析代碼是一個比較無聊、複雜而又繁瑣的過程。這種複雜和繁瑣是來因爲編程語言自己語法設計的繁瑣和複雜致使的。好比咱們前文討論過的 Lisp 因爲語法設計的很是簡單、一致而又無歧義,因此解析起來很是輕鬆,可是做爲代價的就是 Lisp 那個被吐槽不少的括號括號括號。
解析代碼通常分紅兩個步驟,第一個步驟是詞法分析,將文本的代碼轉化成一個個 Token。看到這裏的你們應該都有一些正則表達式的基礎吧,在解析代碼的過程當中,咱們須要用正則來分詞作詞法分析。在編譯原理面咱們學習正則的時候就不只僅是學習正則表達式了,也會學習正則的內核 DFA,不過這部分難度不大就是了。
解析代碼的第二個步驟是語法分析,語法分析是將咱們上面詞法分析出的 Token 轉化成 AST。語法分析咱們要學習上下文無關文法(CFG),而且能夠用 BNF 這個表示。CFG 比正則表達能力更強,強在 CFG 能表達遞歸結構,常見的遞歸結構有表達式和代碼塊。在語法分析這個部分,會基本的 LL(1) 算法,可以對自頂向下的分析有足夠的瞭解,就已經足夠了。
不管是正則仍是 CFG,他們都是在用一種形式語言(咱們的編程語言也是一種形式語言),來描述一種抽象結構,因此在學習的過程當中,腦子裏面必定要這種從抽象結構的概念,可以事半功倍。
Parser 在編譯原理裏面是難點但卻不是重點,因此在這一部分你們以爲複雜的算法徹底能夠跳過,不建議浪費太多時間。Parser 都是能夠根據正則和 CFG 自動生成的,並不須要本身手寫。因此這部分主要目的是學好的是正則和 CFG,那些複雜的算法學起來意義很小。
最後還有一個很是有趣的現象,正則表達式是上下文無關文法,而 BNF 卻又是正則文法,你們能夠想一想爲何?
推薦閱讀:
推薦工具:
其實在大多數眼裏的編譯原理,都停留在 Parser 這個階段,由於大部分人都在學習的時候卡在了個這個階段。可是事實上 Parser 不過是這個領域最表面的一層技術而已。編程語言從 AST 纔算正是開始,只有到了 AST 的階段,咱們的計算機才能夠對咱們的編程語言進行包括分析、解釋或者翻譯,而咱們前面咱們所辛辛苦苦寫的代碼只不過是給咱們這些愚蠢的人類看的罷了。
對編程語言 AST 的分析、轉換、解釋以及翻譯理應是編譯原理中最重要的一個部分,但因爲咱們經典編譯原理書出版時間都比較早(1985年),而且也只着眼於當時流行的以 C 爲主的編譯型語言,因此它的重點都放在瞭解析代碼和生成彙編兩個部分。可是以如今的編程語言角度來看的話,前端有 Parser Generator,後端有 LLVM 那麼咱們更多的重點其實應該跟多地放在中端上來。
不過到這裏爲止,咱們介紹的內容其實已經足夠大部分小夥伴給本身寫個 DSL,給本身寫一個編譯到 JavaScript 的小語言玩了。 可是這足夠了嗎?咱們到底能夠對 AST 作些什麼呢?讓咱們下篇再見吧。
參考項目: