學習筆記之編程達到一個高的境界就是自制腳本語言(圖)
編程達到一個高的境界就是自制腳本語言,經過這能夠精通編程裏面的高深的技術,如編譯原理、語言處理器、編譯器與解釋器,這些都是表明一個程序員實力的技術。
每一個程序員都有實現屬於本身編程語言的夢想,說其是夢想,緣由是實現的難度很大......這種狀況一直持續到《自制編程語言》的出現。
《自制編程語言》鄭鋼著
本書講的是純粹的技術「乾貨」,符合鄭鋼老師一向的寫做風格,這是他靜心寫出來的東西,內容滿滿,很值得閱讀。滴滴系統部技術高級總監於曉聲說:「很高興能成爲本書的首批讀者,也很高興能爲本書寫推薦序。」
剛拿到本書手稿時,從書名上我意識到這是對我胃口的書。果真,整書閱讀之後,收穫頗多。現在程序員的開發成本已經很低了,項目中有各類成熟的框架和庫可供選擇和使用,但還有人能靜下心來研究編譯器這麼底層的技術,實屬可貴。本書猶如一把火炬,點燃了技術人心裏對開發的熱情。
依稀記得2010年年初在百度與鄭鋼初次見面的情景,那時他工做之餘的時間基本都用在向各個技術專家請教、討論各種技術問題上,他是我帶過的人中最勤奮的人之一。時間荏苒,一分耕耘一分收穫,看到他今天的成長,尤感欣慰。html
本書講述了一門腳本語言(sparrow)的開發過程,這是一本「步步爲營」式的書籍,延續了他編寫《操做系統真象還原》的風格,手把手地教讀者從零實現一門語言,從原理到實踐每一步都有實際的代碼和詳盡的原理說明,經過運行書中各小節中的代碼,讀者能夠很輕鬆地掌握各個細節,所以本書的學習曲線並不陡峭,甚至很平坦。
另外,值得欣喜的是,本書所編寫的腳本語言並非用Java、C++等入門難度略大的語言實現的,而是用C語言,這是咱們學習編程的基礎語言。也就是說,本書並不須要專業的開發經驗便可上手學習。另外,在實現過程當中並未用到複雜的庫函數或系統調用,能夠負責地說,本書已經將學習成本降到最低。
C語言是一種面向過程的語言,如何用一種面向過程的語言去實現一種面向對象的語言頗有意思。另外,PHP和Perl語言雖然也實現了類,但它們實際上是一種面向過程的語言,並非純粹的面嚮對象語言,而sparrow語言是一種純粹的面嚮對象語言,它在設計之初就採用對象的方式來處理腳本語言中類的成員和方法,這彷彿讓咱們看到了面向對象編程語言的基因。
衆所周知,當今最流行的腳本語言應屬Python,Python也是用C語言實現的,也許你很好奇Python的內部原理,可是想到它有將近 4 萬行的源代碼時,也許甚至不想看它的源程序了。那麼研讀本書中的sparrow語言會是一種更好的選擇,其源碼不足7100行,閱讀過程輕鬆愉快,但能夠學到Python這種語言的實現原理。
對於腳本語言來講,兩個重要方面就是垃圾回收和運行環境。垃圾回收就是咱們平時所說的GC(Garbage Collection)。有了GC,程序員不須要手工釋放所分配的對象,可使精力專一於業務邏輯而不用擔憂內存泄漏問題。
在sparrow語言中一樣實現了GC,經過此部分代碼你能夠看到GC 的原理,以及哪些對象才能被回收。 運行時環境就是腳本語言中的虛擬機,即VM(如Java語言的JVM也是一種VM)。
腳本語言是經過虛擬機才能運行的,如何把編譯器生成的操做碼轉換爲實際的代碼行爲,這裏面的工做對大多數人來講很神祕。相信各位在源碼中一探究竟以後會發現:GC和VM這兩個神祕的黑盒子不過如此。
另外,也許程序員最感興趣的就是線程,關於線程在用戶態下是如何實現的、線程如何實現調度,本書將告訴你答案。總之,但凡涉獵,開卷有益。
爲何創做這本書?
不少讀者看了我寫的《操做系統真象還原》(一本一步步編寫操做系統的書)書後,紛紛來信,要求我再寫一本自制編程語言的書。這也在情理之中,對於不少計算機從業者來講,操做系統和編譯器幾乎是兩座沒法逾越的大山,其難度之大,令不少人員望而生畏。最終,在讀者的鼓勵下,一衝動就答應了寫做本書,其實我很「後悔」作出這樣的決定。
爲何後悔呢?由於寫書代價很大。
首先,寫書至關累,佔用不少精力。其次,佔用本身學習的時間,在當今我的進步緩慢就算退步的時代,本身沒有提高技術會很恐慌。
再次,精力全放在寫書上會影響家庭、影響工做。
最後,還要負責解答許多問題,確實很累。並且,這一次但是在創造編程語言,難度係數過高了,不亞於開發一個操做系統,甚至我父母都勸我:小剛,你都多大了還寫書,好好過日子、踏實上班就好了。可是,我最後仍是決定寫本書。
下面是我跨太重重攔阻創做本書的動機。
1 有夢想,有遠方
既然寫書代價那麼大,那我爲何還要「明知山有虎,偏向虎山行」呢?由於我就是奔着「老虎」去的,沒有老虎的山就沒有探險的樂趣。
2 有難度纔有價值
每次遇到一件很難的工做時,我先是「痛苦」,而後隨之而來的就是「興奮」,由於這意味着我要進步。也許讀者會說,必定會進步嗎?也許99%會失敗。
一樣一件事,每一個人對它的態度都不一樣,懦夫看到的是:99%會失敗,別幹了。勇士看到的是:還有1%成功的機會,幹吧!
只要不放棄(注意,不是堅持),必定會成功,成功只是時間長短的問題。
3 人生的意義
人生最大的遺憾是「壯志未酬」。若是你是天才,請將本身的才華「揮霍」得一滴不剩,直到觸碰到本身智力上的天花板,這樣才甘心。若是你是大力士,請努力在奧運賽場上爲國爭光,直到累得站不起來,這樣才甘心。
這正是我寫本書的信仰。
學習很累而且無止境,可是多知道一些就會有多一些的欣喜。本着「把本身的知識多掏點給你們」的誠意,本書依然從第0章開始,相對《操做系統真象還原》來講,本書的語言再也不那麼活潑(囉唆)了,畢竟編譯器的開發難度略小於開發操做系統,不必穿插一些「過渡」的話題。
本書一步步地實現了一種稱爲sparrow的編程語言,它是用虛擬機運行的,所以最後還要實現一個虛擬機。sparrow語言是用C語言編寫的,學習的難度較低,實現的代碼不長,但願你們在學習的旅途中愉快。
爲何讀這本書?
本書是一本專門介紹自制編程語言的圖書,書中深刻淺出地講述瞭如何開發一門編程語言,以及運行這門編程語言的虛擬機。
本書主要內容包括:腳本語言的功能、詞法分析器、類、對象、原生方法、自上而下算符優先、語法分析、語義分析、虛擬機、內建類、垃圾回收、命令行及調試等技術。
本書適合程序員閱讀,也適合對編程語言原理感興趣的計算機從業人員學習。
成功的基石不是堅持,而是「不放棄」
人們常說,堅持是成功的「前提」。我說,既然只是前提,這說明堅持也未必會成功。要想成功,人們須要的是成功的「基石」,而不是「前提」,這個基石就是3個字:不放棄。
大部分讀者都以爲開發一門編程語言是很難的事,甚至想都不敢想,我擔憂你也有這個想法,因此特地用這種方式先和你說說內心話:這本書你買都買了,多少發揮點價值纔對得起買書的錢,誰的錢也不是白來的。
首先,我並不會爲了鼓勵你們而大言不慚地說開發語言「其實不難」「很容易」之類的話,相反,這個方向確實很難,並且就應該很難,我想這也正是吸引你的地方,沒有難度哪來的價值,「其實不難、很容易」之類的話是對你們上進心的不尊重。
其次,只有在「我也認爲很難」的前提下才能保證大部分的朋友能看懂本書。你看,在普通人眼裏從A到D,須要有B和C的推理過程,一個步驟都不能少,在天才眼裏,A到D是理所應當的事,不須要解釋得太清楚,天才認爲B和C都是廢話,明擺着的事不須要解釋。而我不是天才,因此我會把B和C解釋清楚。
回到開頭的話,爲何說成功的基石不是「堅持」而是「不放棄」呢?這兩個詞有啥區別?也許有讀者說,不放棄就是作着喜歡的事,讓本身愛上學習技術。我的以爲這有點不對了,我以爲我更喜歡吃喝玩樂,由於那是生物的本能,選擇技術的緣由只是我沒那麼討厭它,它是我從衆多討厭的事物中選擇的最不討厭的東西。
放棄是爲了減小痛苦,堅持是帶着痛苦繼續前行。「堅持」是個痛苦的詞,但凡靠堅持來作的事情必然創建在痛苦之上,而痛苦就會令人產生放棄的念頭,這是生物的本能。用「堅持」來「鼓勵」本身硬着頭皮幹,其實已經輸了一半,本身認爲痛苦的事很難幹下去,幹不下去的緣由是遇到困難時頭腦裏有「放棄」的念頭,若是把這個念頭去掉,那麼,只要活着,成功無非是時間長短的問題。這個念頭其實就是心理預期,「提早」作好心理預期很重要。
總之,不要給本身「能夠放棄」的念頭,不要讓「能夠放棄」成爲一種選項,把這個選項去掉,那麼,只剩下成功。
你懂編程語言的「心」嗎
先來猜猜這是什麼?
它是一種人人必不可少,擁有多種顏色、多種外形的物品。
它是一種質地柔軟,可以使人免受風寒,給予人們溫暖的平常物品。
它是一種令人更加美麗,更受年輕女性歡迎的物品。
它是一種用鈕釦、拉鍊或繩帶綁定到身體上的物品。
猜到了嗎?其實這是對「衣服」的描述。因爲咱們都知道什麼是衣服,所以咱們認爲以上4種描述都是正確的,經過「免受風寒」這4個字便有可能想到是衣服。但對於沒見過衣服的人,好比剛出生的小孩兒,他確定仍是不懂,甚至不知道什麼是鈕釦。
什麼是編程語言呢?如下摘自百度百科。
(1)「編程語言"(programming language),是用來定義計算機程序的形式語言。它是一種被標準化的交流技巧,用來向計算機發出指令……
(2)編程語言的描述通常能夠分爲語法及語義。語法是說明編程語言中,哪些符號或文字的組合方式是正確的,語義則是對於編程的解釋……
(3)編程語言俗稱「計算機語言」,種類很是多,總的來講能夠分紅機器語言、彙編語言、高級語言三大類。程序是計算機要執行的指令的集合,而程序所有都是用咱們所掌握的語言來編寫的……
就像剛纔我對衣服的描述,以上的3個概念,懂的人早已經懂了,不懂的人仍是不懂,回答顯得很「雞肋」。由於對於編程語言的理解並不在語言自己,而是在編譯器,編譯器是編程語言的「心」,而咱們不多有人像瞭解衣服那樣瞭解編譯器,所以對於咱們大多數人來講只是熟悉了語言的語法,僅僅是「會用」而已。
那什麼是編程語言呢?不管我用多少文字都不足以表述精準與全面,由於語言的本質就是編譯器,等你瞭解編譯器後,答案自在心中。目前我只能給出一樣「雞肋」的答案—編程語言是編譯器用來「將人類思想轉換爲計算機行爲」的語法規則。
編程語言的來歷
世界上本沒有編程語言,有的只是編譯器。語言自己只是一系列的語法規則, 這個規則對應的「行爲」纔是咱們編程的「意圖」,所以從「規則」到「行爲」解析即是語言的本質,這就是編譯器所作的工做。
估計大夥兒都知道,若是想輸出字符串,在PHP語言中能夠用語句echo,在C語言中使用printf函數,在C++中使用cout,這說明不一樣的規則對應相同的行爲,所以語言規則的多樣性只是迷惑人的外表,而本質的行爲都是同樣的,萬變不離其宗。
並非「打印」功能就必定得是print、out等相關的字眼兒,那是編譯器的設計者爲了用戶使用方便(固然也是爲了他本身設計方便)而採用了大夥兒有共識的關鍵字,避免沒必要要的混亂。
語言必定要用更底層的語言來編寫嗎
有這個疑問並不奇怪,好比:
(1)Python是用C寫的,C較Python來講更適合底層執行。
(2)C代碼在編譯後會轉換爲更底層的彙編代碼給彙編器,再由彙編器將彙編代碼轉換爲機器碼。
所以給人的感受是,一種語言必需要用更底層的語言來實現,其實這是個誤解。C只是起初是用匯編語言寫的,由於在C語言以前只有彙編語言和機器語言。人老是懶惰的,確定是挑最方便的用,彙編語言好歹是機器語言的符號化,所以相對來講更好用一些,因此只好用匯編來編寫C語言,等初版C語言誕生後,他們就用C語言來寫了。
什麼?用C來編寫C?有些讀者心裏就崩潰了,彷佛像是陷入了死循環。其實這根本不是一回事,由於起做用的並非C語言,而是C編譯器。語言只是規則,編譯器產生的行爲纔是最關鍵的,編譯器就是個程序,C代碼只是它的文本輸入。用C來編寫C,這就是自舉,假如編譯器是用別的語言寫的,也許你內心就好受一些了。
其實只要所使用的語言具備必定的寫文件功能就可以寫編譯器,爲何這麼說呢?由於編譯器自己是程序,程序自己是由操做系統加載執行的,操做系統識別程序的格式後按照格式讀取程序中的段並加載到內存,最後使程序計數器(寄存器pc或ip)跳到程序入口,該程序就執行了。
所以用來編寫編譯器的語言只要具備必定程度的寫文件的能力便可,好比至少要具備形同seek的文件定位功能,這可用於按照不一樣格式的協議在不一樣的偏移處寫入數據,所以用Python是能夠寫出C編譯器的。在這以前我寫過《操做系統真象還原》一書,裏面的第0章第0.17小節「先有的語言仍是先有的編譯器,第1個編譯器是怎麼產生的」,詳細地說明C編譯器是如何自舉的,下面我把它貼過來。
首先確定的是先有的編程語言,哪怕這個語言簡單到只有一個符號。先是設計好語言的規則,而後編寫可以識別這套規則的編譯器,不然若沒有語言規則做爲指導方向,編譯器的編寫將無從下筆。第1個編譯器是怎麼產生的,這個問題我並無求證,不過能夠談下本身的理解,請大夥兒辯證地看。
這個問題屬於哲學中雞生蛋,蛋生雞的問題,這種思惟迴旋性質的本源問題常常讓人產生迷惑。但是現實生活中這樣的例子太多了,具體以下。
(1)英語老師教學生英語,學生成了英語老師後又能夠教其餘學生英語。
(2)寫新的書須要參考其餘舊書,新的書未來又會被更新的書參考,就像本書編寫過程同樣,要參考許多前輩的著做。
(3)用工具能夠製造工具,被製造出來的工具未來又能夠製造新的工具。
(4)編譯器能夠編譯出新的編譯器。
這種本身創造本身的現象,稱爲自舉。
自舉?是否是本身把本身舉起來?是的,人是不能把本身舉起來的,這個詞很形象地描述了這類「後果必須有前因」的現象。
以上前3個舉的都是生活例子,彷佛比第4個更容易接受。即便這樣,對於前3個例子你們依然會有疑問:
(1)第一個會英語的人是誰教的?
(2)第一本書是怎樣產生的?
(3)第一個工具是如何製造出來的?
其實看到第(2)個例子你們就可能明白了。世界上的第一本書,它的知識來源確定是人的記憶,經過向我的或羣衆打聽,把你們都認同的知識記錄到某個介質上,這樣第一本書就出生了。此後再記錄新的知識時,因爲有了這本書的參考,不須要從新再向衆人打聽原有知識了,今後之後便造成了書生書的因果循環。
從書的例子能夠證實,本源問題中的第一個,都是由其餘事物建立出來的,不是本身創造的本身。
就像先有雞仍是先有蛋同樣,必定是先有的其餘生命體,這個生命體不是今天所說的雞。伴隨這個生命體漫長的進化中,忽然有一天具有了生蛋的能力(也許這個蛋在最初並不能孵化成雞,這個生命體又通過漫長的進化,最終能夠生出可以孵化成雞的蛋),因而這個蛋能夠生出雞了。過了好久以後,纔有的人類。人一開始便接觸的是如今的雞而不知道那個生命體的存在,因此人只知道雞是由蛋生出來的。
很容易讓人混淆的是編譯C語言時,它先是被編譯成彙編代碼,再由彙編代碼編譯爲機器碼,這樣很容易讓人誤覺得一種語言是基於一種更底層的語言的。彷佛沒有彙編語言,C語言就沒有辦法編譯同樣。拿gcc來講,其內部確實要調用匯編器來完成彙編語言到機器碼的翻譯工做。由於已經有了彙編語言編譯器,那何須浪費這個資源不用,本身非要把C語言直接翻譯成機器碼呢,畢竟彙編器已經無比健壯了,將C直接變成機器碼這個難度比將C語言翻譯爲彙編語言大多了,這屬於從新造輪子的行爲。
曾經我就這樣問過本身,PHP解釋器是用C語言寫的,C編譯器是用匯編語言寫的(這句話不正確),彙編語言是誰寫的呢?後來才知道,編譯器gcc實際上是用C語言寫的。乍一聽,什麼?用C語言寫C編譯器?本身創造本身,就像電影《超驗駭客》同樣。當時的思惟彷佛陷入了死循環同樣,如今看來這不奇怪。其實編譯器用什麼語言寫是無所謂的,關鍵是能編譯出指令就好了。
編譯出的可執行文件是要寫到磁盤上的,理論上,某個進程,不管其是否是編譯器,只要其關於讀寫文件的功能足夠強大,能夠往磁盤上寫任意內容,均可以生成可執行文件,直接讓操做系統加載運行。想象一下,用Python寫一個腳本,功能是複製一個二進制可執行文件,新複製出來的文件確定是能夠執行的。那Python腳本直接輸出這樣的一個二進制可執行文件,它天然就是能夠直接執行的,徹底脫離Python解釋器了。
編譯器其實就是語言,由於編譯器在設計之初就是先要規劃好某種語言,根據這個語言規則來寫合適的編譯器。因此說,要發明一種語言,關鍵是得寫出與之配套的編譯器,這二者是同時出來的。最初的編譯器確定是簡單、粗糙的,由於當時的編程語言確定不完善,頂可能是幾個符號而已,因此難以稱之爲語言。只有功能完善且符合規範,有本身一套體系後才能稱之爲語言。
不用說,這個最初的編譯器確定沒法編譯今天的C語言代碼。編程語言只是文本,文本只是用來看的,沒有執行能力。最初的編譯器確定是用機器碼寫出來的。這個編譯器能識別文本,能夠處理一些符號關鍵字。隨着符號愈來愈多,不斷地去改進這個編譯器就是了。
以上的符號說的就是編程語言。後來這個編譯器支持的關鍵字愈來愈多了,也就是這個編譯器支持的編程語言愈加強大了,能夠寫出一些複雜的功能的時候,乾脆直接用這個語言寫個新的編譯器,這個新的編譯器出生時,仍是須要用舊的編譯器編譯出來的。
只要有了新的編譯器,以後就能夠和舊的編譯器說拜拜了。發明新的編譯器實際上就是可以處理更多的符號關鍵字,也就是又有新的開發語言了,這門語言能夠是全新的也能夠是最初的語言,這取決於編譯器的實現。這個過程不斷持續,不斷進化,逐漸纔有了今天的各類語言解釋器,這是個迭代的過程。
圖 0-1
圖0-1在網絡上很是火,它經常與勵志類的文字相關。起初看到這個雕像在雕刻本身時,我着實被感動了,感覺到的是一種成長之痛。弟子規讀後感(http://www.simayi.net/duhougan/6660.html)心得體會,今天把它貼過來的目的是想告訴你們,起初的編譯器也是功能簡單,不成規範的,然而通過不斷自我「雕刻」,它纔有了今天功能的完善。
下面的內容我參考了別人的文章,因爲找不到這位大師的署名,只好在此先獻上我真摯的敬意,感謝他對求知者的奉獻。
要說到C編譯器的發展,必需要提到這兩位大神—C語言之父Dennis Ritchie和Ken Thompson。Dennis和Ken在編程語言和操做系統的深遠貢獻讓他們得到了計算機科學的最高榮譽,Dennis和Ken於1983年贏得了ACM圖靈獎。
編譯器是靠不斷學習,不斷積累才發展起來的,這是自我學習的過程。下面來看看他們是如何讓編譯器長大的。
咱們都知道轉義字符,轉義字符是以\開頭的多個字符,一般表示某些控制字符,它們一般是不可鍵入的,也就是這些字符沒法在鍵盤上直接輸入,好比\n表示回車換行,\t表示tab。因爲以\開頭的字符表示轉義,所以要想表示\字符自己,就約定用\來轉義本身,即\\表示字符\。轉義字符雖然表示的是單個字符的意義,在編譯器眼裏轉義字符是多個字符組成的字符串,好比\n是字符\和n組成的字符串。
起初的C編譯器中並無處理轉義字符,爲敘述方便,咱們如今稱之爲舊編譯器。若是待編譯的代碼文件中有字符串\\,這在舊編譯器眼裏就是\\字符串,並非轉義後的單個字符\。爲了代表編譯器與做爲其輸入的代碼文件的關係,咱們稱「做爲輸入的代碼文件」爲應用程序文件。儘管被編譯的代碼文件是實現了一個編譯器,而在編譯器眼裏,它只是一個應用程序級的角色。例如,gcc –c a.c中,a.c就是應用程序文件。
如今想在編譯器中添加對轉義字符的支持,那就須要修改舊編譯器的源代碼,假設舊編譯器的源代碼文件名爲compile_old.c。被修改後的編譯器代碼,已不屬於舊編譯器的源代碼,故咱們命名其文件名爲compile_new_a.c,圖0-2是修改後的內容。程序員