編程達(dá)到一個高的境界就是自制腳本語言,通過這可以精通編程里面的高深的技術(shù),如編譯原理、語言處理器、編譯器與解釋器,這些都是代表一個程序員實(shí)力的技術(shù)。 每個程序員都有實(shí)現(xiàn)屬于自己編程語言的夢想,說其是夢想,原因是實(shí)現(xiàn)的難度很大......這種情況一直持續(xù)到《自制編程語言》的出現(xiàn)。 ![]() 《自制編程語言》 鄭鋼 著 本書講的是純粹的技術(shù)“干貨”,符合鄭鋼老師一貫的寫作風(fēng)格,這是他靜心寫出來的東西,內(nèi)容滿滿,很值得閱讀。滴滴系統(tǒng)部技術(shù)高級總監(jiān)于曉聲說:“很高興能成為本書的首批讀者,也很高興能為本書寫推薦序?!?/p> 剛拿到本書手稿時,從書名上我意識到這是對我胃口的書。果然,整書閱讀以后,收獲頗多。如今程序員的開發(fā)成本已經(jīng)很低了,項(xiàng)目中有各種成熟的框架和庫可供選擇和使用,但還有人能靜下心來研究編譯器這么底層的技術(shù),實(shí)屬難得。本書猶如一把火炬,點(diǎn)燃了技術(shù)人內(nèi)心對開發(fā)的熱情。 依稀記得2010年年初在百度與鄭鋼初次見面的情景,那時他工作之余的時間基本都用在向各個技術(shù)專家請教、討論各類技術(shù)問題上,他是我?guī)н^的人中最勤奮的人之一。時間荏苒,一分耕耘一分收獲,看到他今天的成長,尤感欣慰。 本書講述了一門腳本語言(sparrow)的開發(fā)過程,這是一本“步步為營”式的書籍,延續(xù)了他編寫《操作系統(tǒng)真象還原》的風(fēng)格,手把手地教讀者從零實(shí)現(xiàn)一門語言,從原理到實(shí)踐每一步都有實(shí)際的代碼和詳盡的原理說明,通過運(yùn)行書中各小節(jié)中的代碼,讀者可以很輕松地掌握各個細(xì)節(jié),因此本書的學(xué)習(xí)曲線并不陡峭,甚至很平坦。 另外,值得欣喜的是,本書所編寫的腳本語言并不是用Java、C++等入門難度略大的語言實(shí)現(xiàn)的,而是用C語言,這是我們學(xué)習(xí)編程的基礎(chǔ)語言。也就是說,本書并不需要專業(yè)的開發(fā)經(jīng)驗(yàn)即可上手學(xué)習(xí)。另外,在實(shí)現(xiàn)過程中并未用到復(fù)雜的庫函數(shù)或系統(tǒng)調(diào)用,可以負(fù)責(zé)地說,本書已經(jīng)將學(xué)習(xí)成本降到最低。 C語言是一種面向過程的語言,如何用一種面向過程的語言去實(shí)現(xiàn)一種面向?qū)ο蟮恼Z言很有意思。另外,PHP和Perl語言雖然也實(shí)現(xiàn)了類,但它們其實(shí)是一種面向過程的語言,并不是純粹的面向?qū)ο笳Z言,而sparrow語言是一種純粹的面向?qū)ο笳Z言,它在設(shè)計之初就采用對象的方式來處理腳本語言中類的成員和方法,這仿佛讓我們看到了面向?qū)ο缶幊陶Z言的基因。 眾所周知,當(dāng)今最流行的腳本語言應(yīng)屬Python,Python也是用C語言實(shí)現(xiàn)的,也許你很好奇Python的內(nèi)部原理,但是想到它有將近 4 萬行的源代碼時,也許甚至不想看它的源程序了。那么研讀本書中的sparrow語言會是一種更好的選擇,其源碼不足7100行,閱讀過程輕松愉快,但可以學(xué)到Python這種語言的實(shí)現(xiàn)原理。 對于腳本語言來說,兩個重要方面就是垃圾回收和運(yùn)行環(huán)境。垃圾回收就是我們平時所說的GC(Garbage Collection)。有了GC,程序員不需要手工釋放所分配的對象,可以使精力專注于業(yè)務(wù)邏輯而不用擔(dān)心內(nèi)存泄漏問題。 在sparrow語言中同樣實(shí)現(xiàn)了GC,通過此部分代碼你可以看到GC 的原理,以及哪些對象才能被回收。 運(yùn)行時環(huán)境就是腳本語言中的虛擬機(jī),即VM(如Java語言的JVM也是一種VM)。 腳本語言是通過虛擬機(jī)才能運(yùn)行的,如何把編譯器生成的操作碼轉(zhuǎn)換為實(shí)際的代碼行為,這里面的工作對大多數(shù)人來說很神秘。相信各位在源碼中一探究竟之后會發(fā)現(xiàn):GC和VM這兩個神秘的黑盒子不過如此。 另外,也許程序員最感興趣的就是線程,關(guān)于線程在用戶態(tài)下是如何實(shí)現(xiàn)的、線程如何實(shí)現(xiàn)調(diào)度,本書將告訴你答案??傊?,但凡涉獵,開卷有益。 為什么創(chuàng)作這本書? 很多讀者看了我寫的《操作系統(tǒng)真象還原》(一本一步步編寫操作系統(tǒng)的書)書后,紛紛來信,要求我再寫一本自制編程語言的書。這也在情理之中,對于很多計算機(jī)從業(yè)者來說,操作系統(tǒng)和編譯器幾乎是兩座無法逾越的大山,其難度之大,令很多人員望而生畏。最終,在讀者的鼓勵下,一沖動就答應(yīng)了寫作本書,其實(shí)我很“后悔”做出這樣的決定。 為什么后悔呢?因?yàn)閷憰鷥r很大。 首先,寫書相當(dāng)累,占用很多精力。其次,占用自己學(xué)習(xí)的時間,在當(dāng)今個人進(jìn)步緩慢就算退步的時代,自己沒有提升技術(shù)會很恐慌。 再次,精力全放在寫書上會影響家庭、影響工作。 最后,還要負(fù)責(zé)解答許多問題,確實(shí)很累。而且,這一次可是在創(chuàng)造編程語言,難度系數(shù)太高了,不亞于開發(fā)一個操作系統(tǒng),甚至我父母都勸我:小剛,你都多大了還寫書,好好過日子、踏實(shí)上班就行了。但是,我最后還是決定寫本書。 下面是我跨過重重攔阻創(chuàng)作本書的動機(jī)。 1 有夢想,有遠(yuǎn)方 既然寫書代價那么大,那我為什么還要“明知山有虎,偏向虎山行”呢?因?yàn)槲揖褪潜贾袄匣ⅰ比サ?,沒有老虎的山就沒有探險的樂趣。 2 有難度才有價值 每次遇到一件很難的工作時,我先是“痛苦”,然后隨之而來的就是“興奮”,因?yàn)檫@意味著我要進(jìn)步。也許讀者會說,一定會進(jìn)步嗎?也許99%會失敗。 同樣一件事,每個人對它的態(tài)度都不同,懦夫看到的是:99%會失敗,別干了。勇士看到的是:還有1%成功的機(jī)會,干吧! 只要不放棄(注意,不是堅持),一定會成功,成功只是時間長短的問題。 3 人生的意義 人生最大的遺憾是“壯志未酬”。如果你是天才,請將自己的才華“揮霍”得一滴不剩,直到觸碰到自己智力上的天花板,這樣才甘心。如果你是大力士,請努力在奧運(yùn)賽場上為國爭光,直到累得站不起來,這樣才甘心。 這正是我寫本書的信仰。 學(xué)習(xí)很累并且無止境,但是多知道一些就會有多一些的欣喜。本著“把自己的知識多掏點(diǎn)給大家”的誠意,本書依然從第0章開始,相對《操作系統(tǒng)真象還原》來說,本書的語言不再那么活潑(啰唆)了,畢竟編譯器的開發(fā)難度略小于開發(fā)操作系統(tǒng),沒必要穿插一些“過渡”的話題。 本書一步步地實(shí)現(xiàn)了一種稱為sparrow的編程語言,它是用虛擬機(jī)運(yùn)行的,因此最后還要實(shí)現(xiàn)一個虛擬機(jī)。sparrow語言是用C語言編寫的,學(xué)習(xí)的難度較低,實(shí)現(xiàn)的代碼不長,希望大家在學(xué)習(xí)的旅途中愉快。 為什么讀這本書? 本書是一本專門介紹自制編程語言的圖書,書中深入淺出地講述了如何開發(fā)一門編程語言,以及運(yùn)行這門編程語言的虛擬機(jī)。 本書主要內(nèi)容包括:腳本語言的功能、詞法分析器、類、對象、原生方法、自上而下算符優(yōu)先、語法分析、語義分析、虛擬機(jī)、內(nèi)建類、垃圾回收、命令行及調(diào)試等技術(shù)。 本書適合程序員閱讀,也適合對編程語言原理感興趣的計算機(jī)從業(yè)人員學(xué)習(xí)。 成功的基石不是堅持,而是“不放棄” 人們常說,堅持是成功的“前提”。我說,既然只是前提,這說明堅持也未必會成功。要想成功,人們需要的是成功的“基石”,而不是“前提”,這個基石就是3個字:不放棄。 大部分讀者都覺得開發(fā)一門編程語言是很難的事,甚至想都不敢想,我擔(dān)心你也有這個想法,所以特意用這種方式先和你說說心里話:這本書你買都買了,多少發(fā)揮點(diǎn)價值才對得起買書的錢,誰的錢也不是白來的。 首先,我并不會為了鼓勵大家而大言不慚地說開發(fā)語言“其實(shí)不難”“很容易”之類的話,相反,這個方向確實(shí)很難,而且就應(yīng)該很難,我想這也正是吸引你的地方,沒有難度哪來的價值,“其實(shí)不難、很容易”之類的話是對大家上進(jìn)心的不尊重。 其次,只有在“我也認(rèn)為很難”的前提下才能保證大部分的朋友能看懂本書。你看,在普通人眼里從A到D,需要有B和C的推理過程,一個步驟都不能少,在天才眼里,A到D是理所應(yīng)當(dāng)?shù)氖?,不需要解釋得太清楚,天才認(rèn)為B和C都是廢話,明擺著的事不需要解釋。而我不是天才,所以我會把B和C解釋清楚。 回到開頭的話,為什么說成功的基石不是“堅持”而是“不放棄”呢?這兩個詞有啥區(qū)別?也許有讀者說,不放棄就是做著喜歡的事,讓自己愛上學(xué)習(xí)技術(shù)。個人覺得這有點(diǎn)不對了,我覺得我更喜歡吃喝玩樂,因?yàn)槟鞘巧锏谋灸?,選擇技術(shù)的原因只是我沒那么討厭它,它是我從眾多討厭的事物中選擇的最不討厭的東西。 放棄是為了減少痛苦,堅持是帶著痛苦繼續(xù)前行?!皥猿帧笔莻€痛苦的詞,但凡靠堅持來做的事情必然建立在痛苦之上,而痛苦就會使人產(chǎn)生放棄的念頭,這是生物的本能。用“堅持”來“鼓勵”自己硬著頭皮干,其實(shí)已經(jīng)輸了一半,自己認(rèn)為痛苦的事很難干下去,干不下去的原因是遇到困難時頭腦里有“放棄”的念頭,如果把這個念頭去掉,那么,只要活著,成功無非是時間長短的問題。這個念頭其實(shí)就是心理預(yù)期,“提前”做好心理預(yù)期很重要。 總之,不要給自己“可以放棄”的念頭,不要讓“可以放棄”成為一種選項(xiàng),把這個選項(xiàng)去掉,那么,只剩下成功。 你懂編程語言的“心”嗎 先來猜猜這是什么? 它是一種人人必不可少,擁有多種顏色、多種外形的物品。 它是一種質(zhì)地柔軟,可使人免受風(fēng)寒,給予人們溫暖的日常物品。 它是一種使人更加美麗,更受年輕女性歡迎的物品。 它是一種用紐扣、拉鏈或繩帶綁定到身體上的物品。 猜到了嗎?其實(shí)這是對“衣服”的描述。由于我們都知道什么是衣服,因此我們認(rèn)為以上4種描述都是正確的,通過“免受風(fēng)寒”這4個字便有可能想到是衣服。但對于沒見過衣服的人,比如剛出生的小孩兒,他肯定還是不懂,甚至不知道什么是紐扣。 什么是編程語言呢?以下摘自百度百科。 (1)“編程語言'(programming language),是用來定義計算機(jī)程序的形式語言。它是一種被標(biāo)準(zhǔn)化的交流技巧,用來向計算機(jī)發(fā)出指令…… (2)編程語言的描述一般可以分為語法及語義。語法是說明編程語言中,哪些符號或文字的組合方式是正確的,語義則是對于編程的解釋…… (3)編程語言俗稱“計算機(jī)語言”,種類非常多,總的來說可以分成機(jī)器語言、匯編語言、高級語言三大類。程序是計算機(jī)要執(zhí)行的指令的集合,而程序全部都是用我們所掌握的語言來編寫的…… 就像剛才我對衣服的描述,以上的3個概念,懂的人早已經(jīng)懂了,不懂的人還是不懂,回答顯得很“雞肋”。因?yàn)閷τ诰幊陶Z言的理解并不在語言本身,而是在編譯器,編譯器是編程語言的“心”,而我們很少有人像了解衣服那樣了解編譯器,因此對于我們大多數(shù)人來說只是熟悉了語言的語法,僅僅是“會用”而已。 那什么是編程語言呢?無論我用多少文字都不足以表述精準(zhǔn)與全面,因?yàn)檎Z言的本質(zhì)就是編譯器,等你了解編譯器后,答案自在心中。目前我只能給出同樣“雞肋”的答案—編程語言是編譯器用來“將人類思想轉(zhuǎn)換為計算機(jī)行為”的語法規(guī)則。 編程語言的來歷 世界上本沒有編程語言,有的只是編譯器。語言本身只是一系列的語法規(guī)則, 這個規(guī)則對應(yīng)的“行為”才是我們編程的“意圖”,因此從“規(guī)則”到“行為”解析便是語言的本質(zhì),這就是編譯器所做的工作。 估計大伙兒都知道,如果想輸出字符串,在PHP語言中可以用語句echo,在C語言中使用printf函數(shù),在C++中使用cout,這說明不同的規(guī)則對應(yīng)相同的行為,因此語言規(guī)則的多樣性只是迷惑人的外表,而本質(zhì)的行為都是一樣的,萬變不離其宗。 并不是“打印”功能就一定得是print、out等相關(guān)的字眼兒,那是編譯器的設(shè)計者為了用戶使用方便(當(dāng)然也是為了他自己設(shè)計方便)而采用了大伙兒有共識的關(guān)鍵字,避免不必要的混亂。 語言一定要用更底層的語言來編寫嗎 有這個疑問并不奇怪,比如: (1)Python是用C寫的,C較Python來說更適合底層執(zhí)行。 (2)C代碼在編譯后會轉(zhuǎn)換為更底層的匯編代碼給匯編器,再由匯編器將匯編代碼轉(zhuǎn)換為機(jī)器碼。 因此給人的感覺是,一種語言必須要用更底層的語言來實(shí)現(xiàn),其實(shí)這是個誤解。C只是起初是用匯編語言寫的,因?yàn)樵贑語言之前只有匯編語言和機(jī)器語言。人總是懶惰的,肯定是挑最方便的用,匯編語言好歹是機(jī)器語言的符號化,因此相對來說更好用一些,所以只好用匯編來編寫C語言,等第一版C語言誕生后,他們就用C語言來寫了。 什么?用C來編寫C?有些讀者內(nèi)心就崩潰了,似乎像是陷入了死循環(huán)。其實(shí)這根本不是一回事,因?yàn)槠鹱饔玫牟⒉皇荂語言,而是C編譯器。語言只是規(guī)則,編譯器產(chǎn)生的行為才是最關(guān)鍵的,編譯器就是個程序,C代碼只是它的文本輸入。用C來編寫C,這就是自舉,假如編譯器是用別的語言寫的,也許你心里就好受一些了。 其實(shí)只要所使用的語言具有一定的寫文件功能就能夠?qū)懢幾g器,為什么這么說呢?因?yàn)榫幾g器本身是程序,程序本身是由操作系統(tǒng)加載執(zhí)行的,操作系統(tǒng)識別程序的格式后按照格式讀取程序中的段并加載到內(nèi)存,最后使程序計數(shù)器(寄存器pc或ip)跳到程序入口,該程序就執(zhí)行了。 因此用來編寫編譯器的語言只要具有一定程度的寫文件的能力即可,比如至少要具有形同seek的文件定位功能,這可用于按照不同格式的協(xié)議在不同的偏移處寫入數(shù)據(jù),因此用Python是可以寫出C編譯器的。在這之前我寫過《操作系統(tǒng)真象還原》一書,里面的第0章第0.17小節(jié)“先有的語言還是先有的編譯器,第1個編譯器是怎么產(chǎn)生的”,詳細(xì)地說明C編譯器是如何自舉的,下面我把它貼過來。 首先肯定的是先有的編程語言,哪怕這個語言簡單到只有一個符號。先是設(shè)計好語言的規(guī)則,然后編寫能夠識別這套規(guī)則的編譯器,否則若沒有語言規(guī)則作為指導(dǎo)方向,編譯器的編寫將無從下筆。第1個編譯器是怎么產(chǎn)生的,這個問題我并沒有求證,不過可以談下自己的理解,請大伙兒辯證地看。 這個問題屬于哲學(xué)中雞生蛋,蛋生雞的問題,這種思維回旋性質(zhì)的本源問題經(jīng)常讓人產(chǎn)生迷惑??墒乾F(xiàn)實(shí)生活中這樣的例子太多了,具體如下。 (1)英語老師教學(xué)生英語,學(xué)生成了英語老師后又可以教其他學(xué)生英語。 (2)寫新的書需要參考其他舊書,新的書將來又會被更新的書參考,就像本書編寫過程一樣,要參考許多前輩的著作。 (3)用工具可以制造工具,被制造出來的工具將來又可以制造新的工具。 (4)編譯器可以編譯出新的編譯器。 這種自己創(chuàng)造自己的現(xiàn)象,稱為自舉。 自舉?是不是自己把自己舉起來?是的,人是不能把自己舉起來的,這個詞很形象地描述了這類“后果必須有前因”的現(xiàn)象。 以上前3個舉的都是生活例子,似乎比第4個更容易接受。即使這樣,對于前3個例子大家依然會有疑問: (1)第一個會英語的人是誰教的? (2)第一本書是怎樣產(chǎn)生的? (3)第一個工具是如何制造出來的? 其實(shí)看到第(2)個例子大家就可能明白了。世界上的第一本書,它的知識來源肯定是人的記憶,通過向個人或群眾打聽,把大家都認(rèn)同的知識記錄到某個介質(zhì)上,這樣第一本書就出生了。此后再記錄新的知識時,由于有了這本書的參考,不需要重新再向眾人打聽原有知識了,從此以后便形成了書生書的因果循環(huán)。 從書的例子可以證明,本源問題中的第一個,都是由其他事物創(chuàng)建出來的,不是自己創(chuàng)造的自己。 就像先有雞還是先有蛋一樣,一定是先有的其他生命體,這個生命體不是今天所說的雞。伴隨這個生命體漫長的進(jìn)化中,突然有一天具備了生蛋的能力(也許這個蛋在最初并不能孵化成雞,這個生命體又經(jīng)過漫長的進(jìn)化,最終可以生出能夠孵化成雞的蛋),于是這個蛋可以生出雞了。過了很久之后,才有的人類。人一開始便接觸的是現(xiàn)在的雞而不知道那個生命體的存在,所以人只知道雞是由蛋生出來的。 很容易讓人混淆的是編譯C語言時,它先是被編譯成匯編代碼,再由匯編代碼編譯為機(jī)器碼,這樣很容易讓人誤以為一種語言是基于一種更底層的語言的。似乎沒有匯編語言,C語言就沒有辦法編譯一樣。拿gcc來說,其內(nèi)部確實(shí)要調(diào)用匯編器來完成匯編語言到機(jī)器碼的翻譯工作。因?yàn)橐呀?jīng)有了匯編語言編譯器,那何必浪費(fèi)這個資源不用,自己非要把C語言直接翻譯成機(jī)器碼呢,畢竟匯編器已經(jīng)無比健壯了,將C直接變成機(jī)器碼這個難度比將C語言翻譯為匯編語言大多了,這屬于重新造輪子的行為。 曾經(jīng)我就這樣問過自己,PHP解釋器是用C語言寫的,C編譯器是用匯編語言寫的(這句話不正確),匯編語言是誰寫的呢?后來才知道,編譯器gcc其實(shí)是用C語言寫的。乍一聽,什么?用C語言寫C編譯器?自己創(chuàng)造自己,就像電影《超驗(yàn)駭客》一樣。當(dāng)時的思維似乎陷入了死循環(huán)一樣,現(xiàn)在看來這不奇怪。其實(shí)編譯器用什么語言寫是無所謂的,關(guān)鍵是能編譯出指令就行了。 編譯出的可執(zhí)行文件是要寫到磁盤上的,理論上,某個進(jìn)程,無論其是不是編譯器,只要其關(guān)于讀寫文件的功能足夠強(qiáng)大,可以往磁盤上寫任意內(nèi)容,都可以生成可執(zhí)行文件,直接讓操作系統(tǒng)加載運(yùn)行。想象一下,用Python寫一個腳本,功能是復(fù)制一個二進(jìn)制可執(zhí)行文件,新復(fù)制出來的文件肯定是可以執(zhí)行的。那Python腳本直接輸出這樣的一個二進(jìn)制可執(zhí)行文件,它自然就是可以直接執(zhí)行的,完全脫離Python解釋器了。 編譯器其實(shí)就是語言,因?yàn)榫幾g器在設(shè)計之初就是先要規(guī)劃好某種語言,根據(jù)這個語言規(guī)則來寫合適的編譯器。所以說,要發(fā)明一種語言,關(guān)鍵是得寫出與之配套的編譯器,這兩者是同時出來的。最初的編譯器肯定是簡單、粗糙的,因?yàn)楫?dāng)時的編程語言肯定不完善,頂多是幾個符號而已,所以難以稱之為語言。只有功能完善且符合規(guī)范,有自己一套體系后才能稱之為語言。 不用說,這個最初的編譯器肯定無法編譯今天的C語言代碼。編程語言只是文本,文本只是用來看的,沒有執(zhí)行能力。最初的編譯器肯定是用機(jī)器碼寫出來的。這個編譯器能識別文本,可以處理一些符號關(guān)鍵字。隨著符號越來越多,不斷地去改進(jìn)這個編譯器就是了。 以上的符號說的就是編程語言。后來這個編譯器支持的關(guān)鍵字越來越多了,也就是這個編譯器支持的編程語言越發(fā)強(qiáng)大了,可以寫出一些復(fù)雜的功能的時候,干脆直接用這個語言寫個新的編譯器,這個新的編譯器出生時,還是需要用舊的編譯器編譯出來的。 只要有了新的編譯器,之后就可以和舊的編譯器說拜拜了。發(fā)明新的編譯器實(shí)際上就是能夠處理更多的符號關(guān)鍵字,也就是又有新的開發(fā)語言了,這門語言可以是全新的也可以是最初的語言,這取決于編譯器的實(shí)現(xiàn)。這個過程不斷持續(xù),不斷進(jìn)化,逐漸才有了今天的各種語言解釋器,這是個迭代的過程。 圖 0-1 圖0-1在網(wǎng)絡(luò)上非?;?,它常常與勵志類的文字相關(guān)。起初看到這個雕像在雕刻自己時,我著實(shí)被感動了,感受到的是一種成長之痛。今天把它貼過來的目的是想告訴大家,起初的編譯器也是功能簡單,不成規(guī)范的,然而經(jīng)過不斷自我“雕刻”,它才有了今天功能的完善。 下面的內(nèi)容我參考了別人的文章,由于找不到這位大師的署名,只好在此先獻(xiàn)上我真摯的敬意,感謝他對求知者的奉獻(xiàn)。 要說到C編譯器的發(fā)展,必須要提到這兩位大神—C語言之父Dennis Ritchie和Ken Thompson。Dennis和Ken在編程語言和操作系統(tǒng)的深遠(yuǎn)貢獻(xiàn)讓他們獲得了計算機(jī)科學(xué)的最高榮譽(yù),Dennis和Ken于1983年贏得了ACM圖靈獎。 編譯器是靠不斷學(xué)習(xí),不斷積累才發(fā)展起來的,這是自我學(xué)習(xí)的過程。下面來看看他們是如何讓編譯器長大的。 我們都知道轉(zhuǎn)義字符,轉(zhuǎn)義字符是以\開頭的多個字符,通常表示某些控制字符,它們通常是不可鍵入的,也就是這些字符無法在鍵盤上直接輸入,比如\n表示回車換行,\t表示tab。由于以\開頭的字符表示轉(zhuǎn)義,因此要想表示\字符本身,就約定用\來轉(zhuǎn)義自己,即\\表示字符\。轉(zhuǎn)義字符雖然表示的是單個字符的意義,在編譯器眼里轉(zhuǎn)義字符是多個字符組成的字符串,比如\n是字符\和n組成的字符串。 起初的C編譯器中并沒有處理轉(zhuǎn)義字符,為敘述方便,我們現(xiàn)在稱之為舊編譯器。如果待編譯的代碼文件中有字符串\\,這在舊編譯器眼里就是\\字符串,并不是轉(zhuǎn)義后的單個字符\。為了表明編譯器與作為其輸入的代碼文件的關(guān)系,我們稱“作為輸入的代碼文件”為應(yīng)用程序文件。盡管被編譯的代碼文件是實(shí)現(xiàn)了一個編譯器,而在編譯器眼里,它只是一個應(yīng)用程序級的角色。例如,gcc –c a.c中,a.c就是應(yīng)用程序文件。 現(xiàn)在想在編譯器中添加對轉(zhuǎn)義字符的支持,那就需要修改舊編譯器的源代碼,假設(shè)舊編譯器的源代碼文件名為compile_old.c。被修改后的編譯器代碼,已不屬于舊編譯器的源代碼,故我們命名其文件名為compile_new_a.c,圖0-2是修改后的內(nèi)容。 代碼compile_new_a.c 圖 0-2 其中,函數(shù)next()的功能是返回待處理文本(即被編譯的源碼文件)中的下一字符,強(qiáng)調(diào)一下是“單個字符”,并不是記法分析中的單詞(即token)。 用舊編譯器將新編譯器的源代碼compile_new_a.c編譯,生成可執(zhí)行文件,該文件就是新的編譯器,我們?nèi)∶麨樾戮幾g器_a。為了方便理清它們的關(guān)系,將它們列入表0-1中。 表 0-1 這下編譯出來的新編譯器_a可以編譯含有轉(zhuǎn)義字符\\的應(yīng)用程序代碼了,也就是說,待編譯的文件(也就是應(yīng)用程序代碼)中,應(yīng)該用\\來表示\。而單獨(dú)的字符\在新編譯器_a中未做處理而無法通過編譯。所以此時新編譯器_a是無法編譯自己的源代碼compile_new_a.c的,因?yàn)樵撛次募兄皇菃蝹€\字符,新編譯器_a只認(rèn)得\\。先更新它們的關(guān)系,見表0-2。 表 0-2 也就是說,現(xiàn)在新編譯器_a,無法編譯自己的源文件compile_new_a.c,只有舊編譯器才能編譯它。再說一下,新編譯器_a無法正確編譯自己的源文件compile_new_a.c的原因是,compile_new_a.c中\(zhòng)字符應(yīng)該用轉(zhuǎn)義字符的方式來引用,即所有用\的地方都應(yīng)該替換為\\。再回頭看一下新編譯器_a的源代碼compile_new_a.c,它只處理了字符串\\,單個\沒有對應(yīng)的處理邏輯。下面修改代碼,將新修改后的代碼命名為compile_new_b.c,如圖0-3所示。 代碼compile_new_b.c 圖 0-3 其實(shí)compile_new_b.c只是更新了轉(zhuǎn)義字符的語法,這是新編譯器_a所支持的新的語法,下面還是以新編譯器_a來編譯新的編譯器。 用新編譯器_a編譯此文件,將生成新編譯器_b,將新的關(guān)系錄入到表0-3中。 表 0-3 繼續(xù)之前再說一下:用編譯器去編譯另一編譯器的源碼,也許有的讀者覺得很費(fèi)解,其實(shí)你把“被編譯的編譯器源碼”當(dāng)成普通的應(yīng)用程序源碼就特別容易理解了。上面的編譯器代碼compile_new_b.c,其第3、6、7行的字符串\\被新編譯器_a處理后,會以單字符\來代替(這是新編譯器_a源碼中return語句的功能),因此最終處理完成后的代碼等同于代碼compile_new_a.c。現(xiàn)在想加上換行符\n的支持,如圖0-4所示。 圖 0-4 由于現(xiàn)在編譯器還不認(rèn)識\n,故這樣做肯定不行,不過可以用其ASCII碼來代替,將其命名為compile_new_c.c,如圖0-5所示。 代碼compile_new_c.c 圖 0-5 用新編譯器_a來編譯compile_new_c.c,將生成新編譯器_c,新編譯器_c的代碼相當(dāng)于代碼compile_new_c.c中所有\(zhòng)\被替換為\后的樣子,如表0-4所列,暫且稱之為代碼compile_new_c1.c,如圖0-6所示。 代碼compile_new_c1.c 圖 0-6 表 0-4 最后再修改compile_new_c.c為compile_new_d.c,將10用\n替代,如圖0-7所示。 代碼compile_new_d.c 圖 0-7 用新編譯器_c編譯compile_new_d.c,生成新編譯器d,將直接識別\n。同理,新編譯器d的代碼相當(dāng)于代碼compile_new_d.c中,所有字符串\\被替換為字符\、字符\n被替換為數(shù)字10后的樣子,即等同于代碼compile_new_c1.c,如表0-5所列。 編譯器經(jīng)過這樣不斷地訓(xùn)練,功能越來越強(qiáng)大,不過占用的空間也越來越大了。 表 0-5 《自制編程語言》 鄭鋼 著 本書全面從腳本語言和虛擬機(jī)介紹開始,講解了詞法分析的實(shí)現(xiàn)、一些底層數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)、符號表及類的結(jié)構(gòu)符號表,常量存儲,局部變量,模塊變量,方法存儲、虛擬機(jī)原理、運(yùn)行時棧實(shí)現(xiàn)、編譯的實(shí)現(xiàn)、語法分析和語法制導(dǎo)自頂向下算符優(yōu)先構(gòu)造規(guī)則、調(diào)試、查看指令流、查看運(yùn)行時棧、給類添加更多的方法、垃圾回收實(shí)現(xiàn)、添加命令行支持命令行接口。 《今日互動》 你最想從這本書中學(xué)到什么?為什么?截止時間7月27日17時,留言+轉(zhuǎn)發(fā)本活動到朋友圈,小編將抽獎選出2名讀者贈送紙書2本,文末留言點(diǎn)贊最多的自動獲得圖書1本(參與活動直達(dá)微信端編程達(dá)到一個高的境界就是自制腳本語言) |
|