Build Your Own Lisp 中文版 (2018年)資料_第1頁(yè)
Build Your Own Lisp 中文版 (2018年)資料_第2頁(yè)
Build Your Own Lisp 中文版 (2018年)資料_第3頁(yè)
Build Your Own Lisp 中文版 (2018年)資料_第4頁(yè)
Build Your Own Lisp 中文版 (2018年)資料_第5頁(yè)
已閱讀5頁(yè),還剩114頁(yè)未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

BuildYourOwn

Lisp中文版

劉陽(yáng)

TableofContents

第零零章-關(guān)于

第零一章?介紹

第零二章-安裝

第零三章?基礎(chǔ)

第零四章?交互

第零五章?編程語(yǔ)言

第零六章?語(yǔ)法分析

第零七章?計(jì)算

第零八章?錯(cuò)誤處理

第零九章?s-表達(dá)式

第零十章-Q-表達(dá)式

第十一章-變量

第十二章?函數(shù)

第十三章-條件分支

第十四章?字符串

第十五章?標(biāo)準(zhǔn)庫(kù)

第十六章-彩蛋

第十七章?附錄

TableofContents

第零零章?關(guān)于1.1

第零一章-介紹1.2

第零二章?安裝1.3

第零三章?基礎(chǔ)1.4

第零四章?交互1.5

第零五章-編程語(yǔ)言1.6

第零六章?語(yǔ)法分析1.7

第零七章-計(jì)算1.8

第零八章?錯(cuò)誤處理1.9

第零九章?S-表達(dá)式1.10

第零十章?Q-表達(dá)式1.11

第H-一章?變量1.12

第十二章?函數(shù)1.13

第十三章?條件分支1.14

第十四章?字符串1.15

第十五章?標(biāo)準(zhǔn)庫(kù)1.16

第十六章?彩蛋1.17

第十七章?附錄1.18

第零零章?關(guān)于

在本書中,你將在學(xué)習(xí)C語(yǔ)言的同時(shí)學(xué)會(huì)編寫你自己的編程語(yǔ)言一一一個(gè)100

0行左右代碼的簡(jiǎn)單Lispo不過我們并不是從零開始編寫的,在代碼中我用到

了一個(gè)外部的庫(kù)來(lái)完成一些初始化的工作。但是剩下的最重要的部分都是我們

一行一行編寫的,而且在本書結(jié)束時(shí),你將會(huì)擁有一個(gè)屬于自己的“麻雀雖小,

五臟俱全”的Lisp。

很多人非常想學(xué)習(xí)C語(yǔ)言,但卻無(wú)從下手。現(xiàn)在大可不必?fù)?dān)心了。如果你能堅(jiān)

持看完本書,我敢保證,至少你將擁有一個(gè)非??岬男抡Z(yǔ)言可以把玩,說(shuō)不定

還能成為一個(gè)熟練的C程序員呢!

原作者(Author):DanielHolden(contact@theorange

duck,com)

?閱讀地址:http:〃buildyourownlisp.com/

?項(xiàng)目主頁(yè):Bui1dYourOwnLisp

?共享協(xié)議:CCBY-NC-SA3.0

譯者(Translator):KSCO(numbksco@gmail.com)

?閱讀地址:https:〃ksco.gitbooks.io/build-your-own-lisp/

?項(xiàng)目主頁(yè):Bui1dYourOwnLispCn

?共享協(xié)議:CCBY-NC-SA3.0

第零一章?介紹

這本書是給誰(shuí)看的

本書是為那些想學(xué)習(xí)C語(yǔ)言,或是想知道怎樣構(gòu)建自己的編程語(yǔ)言的人編寫的。

本書不適合作為第一本編程入門書籍。但是如果你之前有一點(diǎn)編程的基礎(chǔ),不

管是什么語(yǔ)言,我相信你都會(huì)在本書中找到一些新奇而有趣的東西。

我會(huì)把本書寫的盡量的通俗易懂,對(duì)初學(xué)者更加友好。但是初學(xué)者可能還是會(huì)

感覺本書的內(nèi)容很有挑戰(zhàn)性。我們會(huì)遇到非常多的新概念,而且還會(huì)同時(shí)學(xué)習(xí)

兩門截然不同的編程語(yǔ)言哦!

在寫代碼遇到問題的時(shí)候,如果到網(wǎng)上尋求幫助,你可能會(huì)發(fā)現(xiàn)人們對(duì)你并不

友好。他們很多時(shí)候并不是在幫助你解決問題,而是炫耀他們?cè)谶@方面的“知

之甚多”。他們可能會(huì)指責(zé)你的錯(cuò)誤,其實(shí)潛臺(tái)詞就是“你不適合寫代碼,不

要再讓你的爛代碼污染這個(gè)世界了”。

在幾次類似的經(jīng)歷之后,你很可能就會(huì)心灰意冷,覺得自己根本算不上一個(gè)程

序員,或是自己壓根兒不喜歡編程。你可能曾經(jīng)想過要編寫一門屬于自己的編

程語(yǔ)言或是其它的有趣而富有挑戰(zhàn)性的項(xiàng)目。但是后來(lái)你意識(shí)到,難度太大了,

我根本就做不到。

對(duì)此,我只能感到抱歉。程序員們可能無(wú)禮、自大、傲慢、脾氣暴躁,但是這

些都無(wú)可指責(zé)。畢竟每天加班累的跟狗一樣,自然需要一個(gè)地方發(fā)泄。但是你

需要知道的是,沒有人是天生就會(huì)的。每個(gè)人都曾經(jīng)是初學(xué)者。都犯過跟你一

樣的低級(jí)錯(cuò)誤。所以,請(qǐng)不要放棄這項(xiàng)富有創(chuàng)造力的工作,而是努力去改變這

個(gè)世界!

為什么要學(xué)習(xí)C語(yǔ)言

C語(yǔ)言是世界上最流行的,最具有影響力的編程語(yǔ)言之一。Linux操作系統(tǒng)就

是用C語(yǔ)言寫成的。AppleOSX和MicrosoftWindows中也大量的用到了C

語(yǔ)言。C語(yǔ)言甚至還被用于微型電腦中一一你家的冰箱和汽車可能就運(yùn)行著使

用C語(yǔ)言寫就的程序。在現(xiàn)代的軟件開發(fā)中,軟件形式的多樣化使得C語(yǔ)言

在很多方面并不是首選,但是學(xué)會(huì)C語(yǔ)言仍然是軟件開發(fā)者的必備技能之一。

C語(yǔ)言是自由的。從Unix,Linux到自由軟件運(yùn)動(dòng),它都起到了舉足輕重的作

用。而C語(yǔ)言本身也同樣是自由的,它將自己所有的東西和盤托出,不藏不掖,

甚至包括了自己的缺點(diǎn)。它幾乎不限制你做任何事情,即使那樣做會(huì)發(fā)生糟糕

的事情。它給了你足夠多的選擇,該怎么做就全看你自己了。

掌握了C語(yǔ)言,就會(huì)懂得什么才是強(qiáng)大,巧妙和自由。在電腦前動(dòng)動(dòng)指尖就會(huì)

讓世界更加美好。

怎樣學(xué)習(xí)c語(yǔ)言

c語(yǔ)言是一門很難學(xué)的語(yǔ)言。它有很多陌生的概念,而且初學(xué)者很難理解透徹。

本書中,我不會(huì)去詳細(xì)的介紹c語(yǔ)言的語(yǔ)法規(guī)則,或是循環(huán)和條件語(yǔ)句的編寫

方法之類的東西。我會(huì)告訴你的是構(gòu)建真實(shí)世界中的C語(yǔ)言程序的方法。這種

方式對(duì)于讀者來(lái)說(shuō)通常更加難以理解,但是卻會(huì)教給你很多傳統(tǒng)方法給不了的

東西。本書并不能保證讓你成為C語(yǔ)言的專家,但至少會(huì)讓你學(xué)會(huì)一些有實(shí)際

意義的知識(shí),而不是學(xué)習(xí)一些沒有意義的程序片段。

本書包含了17個(gè)短小精練的章節(jié)。你應(yīng)該安排好你的閱讀進(jìn)度,每天按照計(jì)

劃閱讀并及時(shí)實(shí)踐。建議每天至少閱讀一個(gè)章節(jié)。讀完本書之后,你還可以繼

續(xù)完善你的Lisp,使它更加完整與強(qiáng)大。

你自己的Lisp

閱讀學(xué)習(xí)本書的最好方式正如同本書的書名,BuildYourOwnLisp。如果你學(xué)

起來(lái)比較輕松,我建議你嘗試添加或修改已有代碼,為語(yǔ)言添加新的特性。雖

然每一章都會(huì)有詳細(xì)解釋,但我還是給出了大量的代碼示例。我知道很多人會(huì)

直接將代碼拷貝到自己的項(xiàng)目中,但是這樣做通常不如自己親自將代碼寫出來(lái)

更加印象深刻。所以,請(qǐng)不要這樣做!

第零二章?安裝

在開始學(xué)習(xí)c語(yǔ)言之前,我們需要安裝一些必要的東西,搭建好編程的環(huán)境。

好在過程并不復(fù)雜,我們只需要兩個(gè)工具:代碼編輯器和編譯器。

代碼編輯器

代碼編輯器其實(shí)就是一個(gè)更適合寫代碼的文本編輯器。它提供一些諸如關(guān)鍵字

高亮,智能提示等功能幫助我們更快更好的編寫代碼。

?在Linux上,我建議你用gedito不過如果你手頭上有現(xiàn)成的代碼

編輯器也是可以的,但請(qǐng)不要使用IDEO殺雞焉用宰牛刀,本書構(gòu)建

的小小程序不會(huì)用到IDE所提供的便利,反而讓你不清楚發(fā)生了什

么。

?在MacOSX上,一個(gè)可以使用的編輯器是TextWrangler,如果你

有其他喜歡的,也是可以的,但請(qǐng)不要使用Xcode,這種小項(xiàng)目使用

IDE反而會(huì)讓你搞不清楚細(xì)節(jié)。

?在MicrosoftWindows上,我建議你使用Notepad++,如果你有其

他喜歡的也是可以的啦。但是請(qǐng)不要使用VisualStudio,因?yàn)樗鼘?duì)

C語(yǔ)言的支持并不好,使用它會(huì)遇到很多問題。

編譯器

編譯器的作用是將我們寫好的c語(yǔ)言的代碼翻譯成電腦能夠直接運(yùn)行的程序。

不同的操作系統(tǒng)安裝編譯器的過程也是有差別的。

另外編譯和運(yùn)行c程序需要知道一些基本的命令行操作,本書不會(huì)教你怎么使

用命令行。如果你從來(lái)沒聽說(shuō)過命令行,你可以到網(wǎng)上搜一些教程看看。

?在Linux上,你可以通過下載并安裝開發(fā)包獲得C語(yǔ)言的編譯器。

如果你的系統(tǒng)是Ubuntu或Debian,你可以通過這行命令來(lái)安裝:s

udoapt-getinstallbuild-essential。

?在MacOSZ上,你需要在應(yīng)用商店里下載并安裝最新版的Xcodeo

然后在命令行中運(yùn)行xcode-select-install來(lái)安裝CommandLine

Tools。

?在MicrosoftWindows上,你可以下載并安裝MinGW,具體的安裝

及配置方法可以到網(wǎng)上搜幾個(gè)教程看一看,這里不再細(xì)說(shuō)。

測(cè)試安裝好的C編譯器

為了驗(yàn)證一下C編譯器是否安裝成功了,請(qǐng)?jiān)诿钚兄墟I入下面的語(yǔ)句并運(yùn)行:

cc-version

如果你得到了一些關(guān)于編譯器版本的信息,那就說(shuō)明安裝成功了!譬如,在我

的Mac上,返回信息如下所示:

$cc--versionAppleLLVMversion7.0.0(clang-700.1.76)Target:x86_64-apple-

darwinl5.0.0Threadmodel:posix

HelloWorld

至此,環(huán)境搭建工作已經(jīng)完成了?,F(xiàn)在打開你的代碼編輯器,將下方的代碼輸

入到其中。新建一個(gè)文件夾,用于存放編寫的代碼。并把剛剛的代碼保存到這

個(gè)文件夾中,起名為hello_world.co這就是我們的第一個(gè)C程序啦!

ttinclude<stdio.h>intmain(intargc,char**argv){puts(,zHello,world!,z);

return0;}

接下來(lái)我會(huì)一行一行的解釋這個(gè)程序。

第一行,我們包含了一個(gè)名為Stdio.h的頭文件進(jìn)來(lái)。這條語(yǔ)句讓我們可以使

用標(biāo)準(zhǔn)輸入輸出庫(kù)所提供的函數(shù)。

接下來(lái),我們聲明一個(gè)名為main的函數(shù),該函數(shù)接受一個(gè)int類型的輸入

argc,和一個(gè)char**類型的輸入argv,返回int類型的值。所有的C程

序都必須包含main函數(shù),它是程序執(zhí)行的起點(diǎn)。

在main函數(shù)當(dāng)中調(diào)用了一個(gè)名為puts的函數(shù)(這個(gè)函數(shù)就是由stdio.h提

供給我們的哦),并傳遞了“Hello,world!”參數(shù),這個(gè)函數(shù)會(huì)將傳進(jìn)去的He

llo,world!輸出到命令行中。puts是putstring的簡(jiǎn)寫形式。函數(shù)中的第

二條語(yǔ)句是return0;。它的作用是結(jié)束main函數(shù)并返回0值。0代表程序

正常退出,沒有發(fā)生錯(cuò)誤。

編譯

在運(yùn)行這個(gè)程序之前,我們首先要將它編譯成可執(zhí)行的程序。打開命令行,然

后跳轉(zhuǎn)到hello_world.c被保存的目錄。你可以通過運(yùn)行以下命令來(lái)編譯你的

程序:

cc-std=c99-Wallhello_world.c-ohello_world

這條語(yǔ)句編譯了hello_world.c里面的代碼,產(chǎn)生了一個(gè)新的可執(zhí)行文件,叫

做hello_worldo-std=c99是為了告訴編譯器我們使用的是哪個(gè)標(biāo)準(zhǔn)的C語(yǔ)言。

通過指定標(biāo)準(zhǔn),只要我們寫的程序符合標(biāo)準(zhǔn)的規(guī)范,我們的程序就可以在多個(gè)

平臺(tái)上編譯并運(yùn)行。

如果編譯成功了,你會(huì)在hello_world.c同目錄下看到一個(gè)名為hello_world

的新文件。在命令行里面敲入./heUo_world,按一下回車鍵,就可以運(yùn)行這

個(gè)程序了。會(huì)在命令行打印出Hello,world!字樣。

恭喜!你成功的編譯并運(yùn)行了你的第一個(gè)C程序!

錯(cuò)誤

如果你的C語(yǔ)言程序?qū)懙拇嬖趩栴},可能會(huì)導(dǎo)致編譯失敗。這個(gè)錯(cuò)誤可能是簡(jiǎn)

單的語(yǔ)法錯(cuò)誤,也可能是其他的一些難以理解的復(fù)雜錯(cuò)誤。

編譯失敗后,編譯器會(huì)向你提供一些有用的錯(cuò)誤信息,如果你看不懂,可以到

網(wǎng)上搜索一下。你要相信,在你之前肯定有很多人遇到了和你一樣的錯(cuò)誤。另

外,程序中可能同時(shí)存在多個(gè)錯(cuò)誤,記得要從第一條開始解決。

而有時(shí),程序雖然編譯成功了,但是運(yùn)行的時(shí)候卻崩潰掉了。這就需要對(duì)程序

進(jìn)行一些調(diào)試性的工作,設(shè)法找出問題所在。調(diào)試程序是進(jìn)階內(nèi)容,超出了本

書的討論范圍。

文檔

在本書提供的代碼中,可能會(huì)遇到一些你從來(lái)沒有見過的函數(shù)。如果想知道這

個(gè)函數(shù)的作用,應(yīng)該去查找C語(yǔ)言標(biāo)準(zhǔn)庫(kù)的在線文檔,這個(gè)文檔中介紹了C

語(yǔ)言中所有的標(biāo)準(zhǔn)庫(kù)函數(shù)的作用及其用法。

參考

參考是用來(lái)干嘛的?

在參考部分,我通常會(huì)給出這章中用到的代碼作為總覽。如果你自己寫的程序

遇到了錯(cuò)誤,請(qǐng)不要把這里的代碼簡(jiǎn)單的拷貝粘貼就完事了。一定要自己嘗試

著找出錯(cuò)誤,并解決它。

hello_world.c

ttinclude<stdio.h>intmain(intargc,char**argv){puts("Hello,world!zz);

return0;}

彩蛋

彩蛋是用來(lái)干嘛的?

在彩蛋部分,我通常會(huì)提出一些有趣的,有挑戰(zhàn)性的問題。請(qǐng)盡力嘗試解決它

們。你沒有必要把它們?nèi)龀鰜?lái),因?yàn)橛行┦呛苡须y度的。

?將"Hello,world!z/改為其他的問候語(yǔ)。

?如果沒有main函數(shù),編譯會(huì)得到什么錯(cuò)誤?

?使用在線文檔查看puts函數(shù)的用法。

第零三章?基礎(chǔ)

這本章中,我會(huì)帶你快速的瀏覽一遍C語(yǔ)言的基本特性(feature)。C語(yǔ)言的

特性非常少,而且語(yǔ)法也相當(dāng)?shù)暮?jiǎn)單明了。但是這并不代表C語(yǔ)言很簡(jiǎn)單,有

深度的內(nèi)容絕不會(huì)浮于表面。所以,接下來(lái)我們將快速地了解一下C語(yǔ)言的基

礎(chǔ),在后面的章節(jié)中再慢慢深入。

本章的目的是讓所有的讀者位于同一水平線上。C的新手可以在本章多花一些

時(shí)間,而已經(jīng)對(duì)C有一定程度了解的同學(xué)則可以大致瀏覽一遍或直接跳過本章。

程序

C語(yǔ)言程序是由函數(shù)定義和類型定義組成的。

因此一個(gè)源文件就是一系列的函數(shù)和類型。每個(gè)函數(shù)都可以調(diào)用其他函數(shù)或調(diào)

用自身(遞歸),可以使用任何已經(jīng)聲明的或內(nèi)建的數(shù)據(jù)類型。

你還可以調(diào)用其他庫(kù)提供的函數(shù),使用庫(kù)中提供的數(shù)據(jù)類型,這也是C中多層

級(jí)系統(tǒng)的復(fù)雜性不斷增長(zhǎng)的根源。

在前一章中,我們提到,所有的程序都是從main函數(shù)開始執(zhí)行的。從main

函數(shù)開始不斷地調(diào)用越來(lái)越多的函數(shù),完成期望的工作。

變量

C中的函數(shù)可以擁有多個(gè)變量。變量其實(shí)就是一個(gè)有名字的數(shù)據(jù)項(xiàng)。

C中的每個(gè)變量都有一個(gè)特定的類型。這些類型可能是內(nèi)建的,也可能是我們

自定義的。聲明一個(gè)新變量的時(shí)候,首先需要將類型的名字寫在前面,然后緊

跟的是變量的名字,你還可以使用=給這個(gè)變量賦一個(gè)初始值。變量的聲明

是一個(gè)涔句,在C語(yǔ)言中,所有的語(yǔ)句必須由;結(jié)尾。

如果要?jiǎng)?chuàng)建一個(gè)名為count的整數(shù)(int),則可以這樣寫:

intcount;

如果要給count賦一個(gè)初始值:

intcount=10;

下面列出了C語(yǔ)言中一些內(nèi)建的類型:

類型名描述舉例

void空類型

char單個(gè)的字符/字節(jié)charlast_initial='H';

int整數(shù)intage=32;

long可以表示更大的數(shù)的整數(shù)longage_of_universe

float浮點(diǎn)數(shù)floatliters_per_pint=0.568f;

double精度更高的浮點(diǎn)數(shù)doublespeed_of_swa11ow=0.01072896;

函數(shù)聲明

函數(shù)是一個(gè)針對(duì)變量的操作過程,同時(shí)可能也會(huì)改變當(dāng)前程序的狀態(tài)。它接受

多個(gè)輸入值,計(jì)算并返回一個(gè)輸出值。

聲明函數(shù)時(shí),首先將返回值的類型寫在前面,后面緊跟函數(shù)的名字,而后的一

對(duì)圓括號(hào)里面包裹函數(shù)的輸入?yún)?shù),參數(shù)之間用,進(jìn)行分割。函數(shù)體部分緊

跟其后,包裹在一對(duì)花括號(hào)(}里,里面包含了函數(shù)執(zhí)行的所有語(yǔ)句,語(yǔ)句之

間使用;分隔。return語(yǔ)句用來(lái)結(jié)束函數(shù)的執(zhí)行,并返回一個(gè)值。

下面的代碼演示了一個(gè)將兩個(gè)int型變量x,y求和之后并返回的函數(shù)寫法。

intaddtogether(intx,inty){intresult二x+y;returnresult;}

調(diào)用函數(shù)時(shí),首先寫上函數(shù)名,然后函數(shù)參數(shù)緊跟其后,包裹在一對(duì)圓括號(hào)里,

參數(shù)之間用逗號(hào)分開。比如說(shuō),我們調(diào)用上面的函數(shù),并將計(jì)算結(jié)果保存到a

dded變量中:

intadded二add_together(10,18);

結(jié)構(gòu)體聲明

結(jié)構(gòu)體用來(lái)聲明一個(gè)新的類型。它可以將多個(gè)變量捆綁在一起。

我們可以使用結(jié)構(gòu)體表示更加復(fù)雜的數(shù)據(jù)類型。例如,為了表示一個(gè)二維空間

里的點(diǎn),我們可以創(chuàng)建一個(gè)名為point的結(jié)構(gòu)體將兩個(gè)float類型的變量x,

y綁在一起。我們可以同時(shí)使用struct和typedef來(lái)聲明一個(gè)結(jié)構(gòu)體:

typedefstruct{floatx;floaty;}point;

注意,我們應(yīng)該將結(jié)構(gòu)體放在所有用到它的函數(shù)的上方。這個(gè)類型和內(nèi)建的基

本數(shù)據(jù)類型的用法沒有任何區(qū)別。獲取結(jié)構(gòu)體內(nèi)部的變量時(shí),需要使用小數(shù)

點(diǎn).,后面緊跟要獲取的變量名:

pointp;p.x=0.1;p.y=10.0;floatlength=sqr.(p.x*p.x+p.y*p.y);

指針

指針類型是普通類型的變體,只需普通類型的后面添加*后綴即可。比如,i

nt*就是一個(gè)int類型的指針。在之前的main函數(shù)的輸入?yún)?shù)列表中,我

們還見過一個(gè)char**類型,這是一個(gè)char類型的指針的指針。

指針是C語(yǔ)言的精髓所在,也是C語(yǔ)言中比較難理解的知識(shí)點(diǎn),我們會(huì)在以

后的章節(jié)中詳細(xì)講解。目前的階段我們還不會(huì)用到指針,所以你只需要知道有

這個(gè)東西就可以了。

字符串

在C語(yǔ)言中,字符串由char*類型表示。它是由一串字符(char)組成的,并

以一個(gè)空終結(jié)字符結(jié)尾。字符串是C語(yǔ)言的一個(gè)重要且復(fù)雜的部分,我們會(huì)在

接下來(lái)的幾章中詳細(xì)的學(xué)習(xí)它。

字符串還可以字面量來(lái)表示,將要表示的字符串包裹在雙引號(hào)〃中就可以了。

比如說(shuō)我們之前用過的“Hello,world!"?,F(xiàn)在,你只需要記住,只要看到ch

ar*類型,就當(dāng)成字符串來(lái)看待就可以了。

條件分支

條件分支語(yǔ)句可以讓程序在只有條件為真的時(shí)候才去執(zhí)行一段代碼。

我們使用if關(guān)鍵字來(lái)聲明條件語(yǔ)句,執(zhí)行條件跟在后面,包裹在一對(duì)圓括號(hào)

里,后面緊跟一對(duì)花括號(hào)里面包裹著條件為真時(shí)執(zhí)行的代碼。if語(yǔ)句后面可

以跟一個(gè)else語(yǔ)句,包含條件為假時(shí)執(zhí)行的代碼。

我們可以使用或兼作壽H)和分操作■多(&&)將多個(gè)條件組合在一起。

在條件語(yǔ)句中,凡是不為0的結(jié)果都是真的。這點(diǎn)需要牢記,因?yàn)楹芏鄺l件測(cè)

試語(yǔ)句都是以此為依據(jù)編寫的。

檢測(cè)x是否在10到100之間的條件語(yǔ)句可以這樣寫:

if(x>10&&x<100){puts(/zxisgreaterthan10andlessthan100!〃);)

else{puts(/?xislessthan11orgreaterthan99!〃);}

循環(huán)

循環(huán)可以不斷的執(zhí)行一段代碼,直到條件變?yōu)榧?,或者?jì)數(shù)完成。

在C語(yǔ)言中有兩種類型的循環(huán)。第一種是while循環(huán)。while循環(huán)不斷地執(zhí)

行一段代碼,直到條件為假停止。首先while關(guān)鍵字在前,后面緊跟包裹在一

對(duì)圓括號(hào)中的條件語(yǔ)句,最后是包裹在一對(duì)大括號(hào)中的待執(zhí)行語(yǔ)句。下面是一

個(gè)例子:

inti=10;while(i>0){puts(Z/LoopIteration");i=i-1;}

第二種循環(huán)是for循環(huán),跟while的條件語(yǔ)句不同的是,for循環(huán)需要三個(gè)

以;隔開的表達(dá)式:一個(gè)初始化語(yǔ)句,一個(gè)條件語(yǔ)句和一個(gè)遞增語(yǔ)句。其中

初始化語(yǔ)句在循環(huán)開始之前執(zhí)行;條件語(yǔ)句每次迭代都會(huì)判斷一次,如果為假,

循環(huán)就退出了;遞增語(yǔ)句在每次迭代的最后執(zhí)行。for循環(huán)通常用來(lái)計(jì)數(shù),因

為它的表示方法比while更加簡(jiǎn)潔。下面的例子中,我們從0遞增到9,每

次加1,共執(zhí)行了10次:

for(inti=0;i<10;i++){puts("LoopIteration'");}

彩蛋

使用for循環(huán)打印5次"Hello,world!”。

使用while循環(huán)打印5次"Hello,world!"。

編寫一■個(gè)函數(shù)Hello,可以打印n次"Helloworld!”,然后在main

函數(shù)中調(diào)用它。

除了變量一節(jié)中列出的內(nèi)建類型,還有哪些呢?

除了〉和〈,還有哪些比較操作符?

除了+和-,還有哪些算數(shù)運(yùn)算符?

+=運(yùn)算符是什么?它是如何工作的?

do...while循環(huán)是是什么?它是如何工作的?

switch語(yǔ)句是什么?它是如何工作的?

break關(guān)鍵字是用來(lái)干嘛的?

?continue關(guān)鍵字是用來(lái)干嘛的?

?typedef關(guān)鍵字的作用是什么?

第零四章?交互

讀取-求值-輸出

在編寫我們的Lisp之前,我們需要尋找一種和它交互的方式。最簡(jiǎn)單的方法,

我們可以修改代碼,重新編譯,然后再次運(yùn)行。這個(gè)方案雖然理論上可行,但

是太為繁瑣。如果可以動(dòng)態(tài)地和程序進(jìn)行交互,我們就可以快速地測(cè)試程序在

各種條件下的行為。我們稱這種模式為交互提示。

這種模式下的程序讀取用戶的輸入,在程序內(nèi)部進(jìn)行處理,然后返回一些信息

給用戶。這種系統(tǒng)也被叫做REPL,是read-evaluate-printloop(讀取-求值

-輸出循環(huán))的簡(jiǎn)寫。這種技術(shù)被廣泛地應(yīng)用在各種編程語(yǔ)言的解釋器中,如果

你學(xué)過Python,那你一定不會(huì)陌生。

在編寫一個(gè)完整的REPL之前,我們先實(shí)現(xiàn)一個(gè)簡(jiǎn)單的程序:讀取用戶的輸入,

簡(jiǎn)單處理后返回給用戶。在后面的章節(jié)中,我們會(huì)對(duì)這個(gè)程序不斷擴(kuò)展,最后

能夠正確地讀取并解析一個(gè)真正的Lisp程序,并將結(jié)果返回給用戶。

交互提示

為了實(shí)現(xiàn)這個(gè)簡(jiǎn)單的想法,可以使用一個(gè)循環(huán)不斷打印信息并等待用戶輸入。

為了獲取用戶輸入的內(nèi)容,我們可以使用stdio.h中的fgets函數(shù)。這個(gè)函

數(shù)可以一直讀取直到遇到換行符為止。我們需要找個(gè)地方存儲(chǔ)用戶的輸入。為

此可以聲明一個(gè)固定大小的數(shù)組緩沖區(qū)。

一旦獲取到用戶輸入的字符串,就可以使用printf將它打印到命令行中。

ttinclude<stdio.h>/*Declareabufferforuserinputofsize2048*/static

charinput[2048];intmain(intargc,char**argv){/*PrintVersionandE

xitInformation*/puts(^LispyVersion0.0.0.0.l,z);puts("PressCtrl+cto

Exit\n〃);/*Inaneverendingloop*/while(1){/*Outputourpro

mpt*/fputs(z,lispy>stdout);/*Readalineofuserinputofmaxi

mumsize2048*/fgets(input,2048,stdin);/*Echoinputbacktouse

r*/printf("Noyou'rea%s〃,input);}return0;}

代碼中的/*…*/是什么?

這是C語(yǔ)言中的注釋,是為了向其它閱讀代碼的人解釋代碼作用的。在編譯的

時(shí)候,會(huì)被編譯器忽略掉。

現(xiàn)在來(lái)深入解讀一下這個(gè)程序。

staticcharinput[2048];這行代碼聲明了一個(gè)擁有2048個(gè)字符長(zhǎng)度的全局?jǐn)?shù)

組。這個(gè)數(shù)組中存儲(chǔ)的數(shù)據(jù)可以在程序的任何地方獲取到。我們會(huì)把用戶在命

令中輸入的語(yǔ)句保存到這里面來(lái)。static關(guān)鍵字標(biāo)明這個(gè)數(shù)組僅在本文件中可

見。[2048]表明了數(shù)組的大小。

我們使用while⑴來(lái)構(gòu)造一個(gè)無(wú)限循環(huán),條件語(yǔ)句1永遠(yuǎn)都為真,所以這個(gè)

循環(huán)會(huì)一直執(zhí)行下去。

我們使用fputs打印提示信息。這個(gè)函數(shù)和前面介紹過的puts函數(shù)區(qū)別是

fputs不會(huì)在末尾自動(dòng)加換行符。我們使用fgets函數(shù)來(lái)獲取用戶在命令行中

輸入的字符串。這兩個(gè)函數(shù)都需要指定寫入或讀取的文件。在這里,我們使用

stdin和stdout作為輸入和輸出。這兩個(gè)變量都是在〈stdio.h〉中定義的,

用來(lái)表示向命令行進(jìn)行輸入和輸出。當(dāng)我們把stdin傳給fgets后,它就會(huì)

等待用戶輸入一串字符,并按下回車鍵。如果得到了字串,就會(huì)把字串連同換

行符存放到input數(shù)組中。為了不讓獲取到的數(shù)據(jù)太大數(shù)組裝不下,我們還要

指定一下可以獲取的最大長(zhǎng)度為2048o

我們使用printf函數(shù)將處理后的信息返回給用戶。printf允許我們同時(shí)打印

多個(gè)不同類型的值。它會(huì)自動(dòng)對(duì)第一個(gè)字符串參數(shù)中的模式進(jìn)行匹配。例如,

在上面的例子中,可以在第一個(gè)參數(shù)中看到%s字樣。printf將自動(dòng)把%s

替換為后面的參數(shù)中的值。s代表字符串(string)。

更多關(guān)于printf的模式種類及其用法,可以參考文檔。

我怎么才能知道一些類似于fgets或printf的函數(shù)的用法?

很明顯你不可能一開始就知道這些標(biāo)準(zhǔn)庫(kù)函數(shù)的作用和用法,這些都需要經(jīng)驗(yàn)。

幸運(yùn)的是,C語(yǔ)言的標(biāo)準(zhǔn)庫(kù)非常精煉。絕大多數(shù)的庫(kù)函數(shù)都可以在平時(shí)的練習(xí)

中了解并學(xué)會(huì)使用。如果你想要解決的是底層的、基本的問題,關(guān)注一下參考

文檔會(huì)大有裨益,因?yàn)楹芸赡軜?biāo)準(zhǔn)庫(kù)中的某個(gè)函數(shù)所做的事情正是你想要的!

編譯

你可以使用在第二章介紹過的命令來(lái)編譯上面的程序:

cc-std=c99-Wallprompt,c-0prompt

編譯通過之后,你應(yīng)該試著運(yùn)行并測(cè)試一下這個(gè)程序。測(cè)試完成后,可以使用

Ctrl+c快捷鍵來(lái)退出程序。如果一切正常,你會(huì)得到類似于下面的結(jié)果:

LispyVersion.1PressCtrl+ctoExitlispy>helloNoYou,reahello1

ispy>mynameisDanNoYou'reamynameisDanlispy>Stopbeingsorude!No

You,reaStopbeingsorude!lispy>

編輯輸入

如果你用的是Mac或Linux,當(dāng)你用左右箭頭鍵編輯在程序中的輸入時(shí),你會(huì)

遇到一個(gè)奇怪的問題:

LispyVersion0.0.0.0.3PressCtrl+ctoExitlispy>hel"[[D^[[C

使用箭頭鍵不會(huì)前后移動(dòng)輸入的光標(biāo),而是會(huì)產(chǎn)生像或.[[C這種奇怪

的字符。很明顯這不是我們想要的結(jié)果。

而在Windows上則不會(huì)有這個(gè)現(xiàn)象。

在Mac和Linux上,我們需要用到editline庫(kù)來(lái)解決這個(gè)問題。并把fput

s和fgets替換為這個(gè)庫(kù)提供的相同功能的函數(shù)。

如果你用的是Windows系統(tǒng),則可以直接跳到本章的最后。因?yàn)榻酉聛?lái)的幾個(gè)

小節(jié)都是和安裝與配置editline相關(guān)的內(nèi)容。

使用editline庫(kù)

我們會(huì)用到editline庫(kù)提供的兩個(gè)函數(shù):readline和add_historyoreadlin

e和fgets一樣,從命令行讀取一行輸入,并且允許用戶使用左右箭頭進(jìn)行

編輯。add_history可以紀(jì)錄下我們之前輸入過的命令,并使用上下箭頭來(lái)獲

取。新的程序如下所示:

ttinclude<stdio.h>#include<stdlib.h>^include<editline/readline.h>ttinclud

e<editline/history.h>intmain(intargc,char**argv){/*PrintVersion

andExitInformation*/puts(z,LispyVersion0.0.0.0.1,/);puts(,zPressCtrl+

ctoExit\n〃);/*Inaneverendingloop*/while(1){

char*input=readline(zzlispy>〃);/*Addi

nputtohistory*/add_history(input);/*Echoinputbacktouser*/

printf(^Noyou'rea%s\n〃,input);/*Freeretrievedinput*/free(in

put);}return0;}

我們?cè)黾恿艘籢些新的頭文件。<stdlib.h>提供了free函數(shù)。<editline/readl

ine.h>和<editline/history.h>提供了editline庫(kù)中的readline和add_h

istory函數(shù)。

在上面的程序中,我們使用readline讀取用戶輸入,使用add_history將該

輸入添加到歷史紀(jì)錄當(dāng)中,最后使用printf將其加工并打印出來(lái)。

與fgets不同的是,readline并不在結(jié)尾添加換行符。所以我們?cè)趐rintf

函數(shù)中添加了一個(gè)換行符。另外,我們還需要使用free函數(shù)手動(dòng)釋放readli

ne函數(shù)返回給我們的緩沖區(qū)input。這是因?yàn)閞eadline不同于fgets函數(shù),

后者使用已經(jīng)存在的空間,而前者會(huì)申請(qǐng)一塊新的內(nèi)存,所以需要手動(dòng)釋放。

內(nèi)存的申請(qǐng)與釋放問題我們會(huì)在后面的章節(jié)中深入討論。

鏈接editline并編譯

如果你使用前面我們提供的命令行來(lái)編譯這個(gè)程序,你會(huì)得到類似于下面的錯(cuò)

誤,因?yàn)樵谑褂弥?,你必須先在電腦上安裝editline庫(kù)。

fatalerror:editline/readline.h:Nosuchfileordirectory#include<editlin

e/readline.h>

在Mac上,editline包含在CommandLineTools中,安裝方法我們?cè)诘诙?/p>

章有說(shuō)明。安裝完后,可能還是會(huì)出現(xiàn)頭文件不存在的編譯錯(cuò)誤。這時(shí),可以

移除^include<editline/history.h>這行代碼,再試一'次。

在Linux上,可以使用sudoapt-getinstalllibedit-dev來(lái)安裝editlineo

在Fedora上,使用su-c〃yuminstalllibedit-dev*〃命令安裝。

一旦你安裝好了editline,你可以再次編譯試一下。然后將會(huì)得到如下的錯(cuò)誤:

undefinedreferenceto\readline'undefinedreferenceto'add_history,

這是因?yàn)闆]有將editline鏈接到程序中。我們需要使用-ledit標(biāo)記來(lái)完成

鏈接,用法如下:

cc-std=c99-Wallprompt,c-ledit-oprompt

再次運(yùn)行程序,就可以自由地編輯輸入的文字了!

為什么我的程序還是不能編譯?

在有些系統(tǒng)上,editline的安裝、包含、鏈接的方式可能會(huì)有些許差別,請(qǐng)善

用搜索引擎哦。

預(yù)處理器

對(duì)于這樣的一個(gè)小程序而言,我們針對(duì)不同的系統(tǒng)編寫不同的代碼是可以的。

但是如果我把我的代碼發(fā)給一個(gè)使用不同的操作系統(tǒng)的朋友,讓他幫我完善一

下代碼,可能就會(huì)出問題了。理想情況下,我希望我的代碼可以在任何操作系

統(tǒng)上編譯并運(yùn)行。這在C語(yǔ)言中是個(gè)很普遍的問題,叫做可移植性(portabili

ty)o這通常都是個(gè)很棘手的問題。

但是C提供了一個(gè)機(jī)制來(lái)幫助我們,叫做預(yù)處理器(preprocessor)。

預(yù)處理器也是一個(gè)程序,它在編譯之前運(yùn)行。它有很多作用。而我們之前就已

經(jīng)悄悄地用過預(yù)處理器了。任何以井號(hào)#開頭的語(yǔ)句都是一個(gè)預(yù)處理命令。

為了使用標(biāo)準(zhǔn)庫(kù)中的函數(shù),我們已經(jīng)用它來(lái)包含(include)過頭文件了。

預(yù)處理的另一個(gè)作用是檢測(cè)當(dāng)前的代碼在哪個(gè)操作系統(tǒng)中運(yùn)行,從而來(lái)產(chǎn)生平

臺(tái)相關(guān)的代碼。而這也正是我們做可移植性工作時(shí)所需要的。

在Windows上,我們可以偽造一■個(gè)readline和add_history函數(shù),而在其他

系統(tǒng)上就使用editline庫(kù)提供給我們的真正有作用的函數(shù)。

為了達(dá)到這個(gè)目的,我們需要把平臺(tái)相關(guān)的代碼包在#ifdef、#else和ttendif

預(yù)處理命令中。如果條件為真,包裹在ttifdef和#else之間的代碼就會(huì)被執(zhí)

行,否則,#else和endif之間的代碼被執(zhí)行。通過這個(gè)特性,我們就能寫出

在Windows、Linux和Mac三大平臺(tái)上都能正確編譯的代碼了:

ttinclude<stdio.h>ttinclude<stdlib.h>/*IfwearecompilingonWindowscomp

ilethesefunctions*/ttifdef_WIN32ttinclude<string.h>staticcharbuffer[2

048];/*Fakereadlinefunction*/char*readline(char*prompt){fputs(pro

mpt,stdout);fgets(buffer,2048,stdin);char*cpy=malloc(strlen(buffe

r)+l);strcpy(cpy,buffer);cpy[strlen(cpy)-1]='\0';returncpy;}/*

Fakeadd_historyfunction*/voidadd_history(char*unused){}/*Otherwisei

ncludetheeditlineheaders*/Seisettinclude<editline/readline.h>^include<

editline/history.h>ttendifintmain(intargc,char**argv){puts(,zLispyV

ersion0.0.0.0.1");puts("PressCtrl+ctoExit'rT);while(){

owineithercasereadlinewillbecorrectlydefined*/char*input=read

line(/zlispy>〃);add_history(input);printf(Z/Noyou'rea%s\n/z,inpu

t);free(input);}return0;}

參考

prompt_unix.c

ttinclude<stdio.h>^include<stdlib.h>#include<editline/readline.h>tfinclud

e<editline/history.h>intmain(intargc,char**argv){/*PrintVersion

andExitInformation*/puts(Z/LispyVersion0.0.0.0.l,z);puts("PressCtrl+

ctoExit\n〃);/*Inaneverendingloop*/while(1){

char*input=readline(zzlispy>〃);/*Addi

nputtohistory*/add_history(input);/*Echoinputbacktouser*/

printf(^Noyou,rea%s\n,z,input);/*Freeretrivedinput*/free(inp

ut);}return0;}

prompt_windows.c

ttinclude<stdio.h>/*Declareabufferforuserinputofsize2048*/static

charinput[2048];intmain(intargc,char**argv){/*PrintVersionandE

xitInformation*/puts(z,LispyVersion0.0.0.0.T');puts(,zPressCtrl+cto

Exit\n〃);/*Inaneverendingloop*/while(1){

mpt*/fputs(z/lispy>stdout);/*Readalineofuserinputofmaxi

mumsize2048*/fgets(input,2048,stdin);/*Echoinputbacktouse

r*/printf("Noyou'rea%s〃,input);}return0;}

prompt,c

^include<stdio.h>ttinclude<stdlib.h>/*IfwearecompilingonWindowscomp

ilethesefunctions*/ttifdef_WIN32ttinclude<string.h>staticcharbuffer[2

048];/*Fakereadlinefunction*/char*readline(char*prompt){fputs(pro

mpt,stdout);fgets(buffer,2048,stdin);char*cpy=malloc(strlen(buffe

r)+l);strcpy(cpy,buffer);cpy[strlen(cpy)-1]='\0';returncpy;}/*

Fakeadd_historyfunction*/voidadd_history(char*unused){}/*Otherwisei

ncludetheeditlineheaders*/Seisettinclude<editline/readline.h>^include<

editline/history.h>ttendifintmain(intargc,char**argv){puts(,zLispyV

ersion0.0.0.0.1");puts("PressCtrl+ctoExit'rT);while(){

owineithercasereadlinewillbecorrectlydefined*/char*input=read

line(/zlispy>〃);add_history(input);printf(Z/Noyou'rea%s\n/z,inpu

t);free(input);}return0;}

彩蛋

?將提示信息lispy〉換成其他你喜歡的。

?修改打印的信息。

?在程序開頭的提示信息中添加一些其他的信息。

?在字符串中,\n表示什么?

?printf還有哪些輸出模式?

?如果你向printf傳遞一個(gè)與模式不匹配的值會(huì)怎樣?

?預(yù)處理器ttifndef有什么用?

?預(yù)處理器^define有什么用?

?_WIN32在Windows中有定義,那在Linux和Mac中定義了什么呢?

第零五章?編程語(yǔ)言

什么是編程語(yǔ)言?

編程語(yǔ)言和自然語(yǔ)言非常相似,也有它背后固有的結(jié)構(gòu)和規(guī)則來(lái)界定語(yǔ)句的正

確性。當(dāng)我們讀寫自然語(yǔ)言時(shí),語(yǔ)言的規(guī)則就在無(wú)意中學(xué)會(huì)了。學(xué)習(xí)編程語(yǔ)言

也是一樣,需要長(zhǎng)久的讀寫練習(xí)才能掌握。一旦掌握,我們就可以利用這些規(guī)

則去理解其他人的代碼,并寫出自己的代碼。

在19世紀(jì)50年代,語(yǔ)言學(xué)家NoamChomsky定義了一系列關(guān)于語(yǔ)言的重要

理論。這些理論支撐了我們今天對(duì)于語(yǔ)言結(jié)構(gòu)的基本理解。其中重要的一條結(jié)

論就是:自然語(yǔ)言都是建立在遞歸和重復(fù)的子結(jié)構(gòu)之上的。

舉例來(lái)說(shuō):

Thecatwalkedonthecarpet.

根據(jù)英語(yǔ)的規(guī)則,名詞cat可以被兩個(gè)由and連接的名詞代替:

Thecatanddogwalkedonthecarpet.

我們可以像之前一樣再次使用這個(gè)規(guī)則,將cat替換為兩個(gè)使用and符號(hào)連

接的新名詞。我們還可以使用另外一個(gè)規(guī)則,將一個(gè)名詞替換為一個(gè)形容詞加

一個(gè)名詞,其中形容詞作為對(duì)名詞的修飾:

Thecatandmouseanddogwalkedonthecarpet.

Thewhitecatandblackdogwalkedonthecarpet.

以上,我們只是簡(jiǎn)單的舉兩個(gè)例子。英語(yǔ)的語(yǔ)法規(guī)則遠(yuǎn)不止于此,漢語(yǔ)的語(yǔ)法

規(guī)則就更復(fù)雜了,呵呵。

我們注意到,在編程語(yǔ)言中也有相似的規(guī)則。在C語(yǔ)言中,if語(yǔ)句可以包含

多條的新語(yǔ)句,新語(yǔ)句當(dāng)然也可以是另一個(gè)if語(yǔ)句。這些遞歸和重復(fù)的規(guī)則

在語(yǔ)言的其他部分也同樣是適用的。

if(x>5){returnx;}

if(x>5){if(x>10){returnx;}}

Chomsky提出的理論是非常重要的。它意味著,雖然一門語(yǔ)言可以表達(dá)無(wú)限的

內(nèi)容,我們?nèi)匀豢梢允褂糜邢薜囊?guī)則去解析所有用該門語(yǔ)言寫就的東西。這些

有限的規(guī)則就叫語(yǔ)法(grammar)o

對(duì)于語(yǔ)法,我們有多種表達(dá)方式。最容易想到的方式就是使用白話文。譬如,

我們可以這樣說(shuō):“句子必須是動(dòng)詞短語(yǔ)〃、”動(dòng)詞詞組可以是動(dòng)詞,也可以是副

詞加動(dòng)詞”等等(譯注:事實(shí)上,這也是我們最初學(xué)習(xí)英語(yǔ)語(yǔ)法的主要方式)。

這種形式對(duì)于人類來(lái)說(shuō)是非常容易理解的,但是對(duì)于計(jì)算機(jī)卻太模糊的、難以

理解的。所以在寫程序時(shí),我們需要對(duì)語(yǔ)法有一個(gè)更標(biāo)準(zhǔn)化的描述。

為了定義一門編程語(yǔ)言(例如我們將要編寫的Lisp),我們首先需要能夠正確解

析用戶按照語(yǔ)法規(guī)則寫就的程序。為此需要編寫一個(gè)涔法解斷器,用來(lái)判斷用

戶的輸入是否合法,并產(chǎn)生解析后的內(nèi)部表示。內(nèi)部表示是一種計(jì)算機(jī)更容易

理解的表示形式,有了它,我們后面的解析、求值等工作會(huì)變得更加的簡(jiǎn)單可

行。

但是這一部分往往是枯燥繁瑣的體力活,我們顯然不希望在這上面浪費(fèi)時(shí)間。

所以我們就采用了一個(gè)叫做mpc的庫(kù)來(lái)幫助我們完成工作。

解析器組合子

mpc是我(原作者)編寫的一個(gè)解析器組合子(ParserCombinators)庫(kù)。這意味

著,你可以使用這個(gè)庫(kù)為任何語(yǔ)言編寫語(yǔ)法解析器。編寫語(yǔ)法解析器的方法有

很多,使用解析器組合子的好處就在于,它極大地簡(jiǎn)化了原本枯燥無(wú)聊的工作,

而僅僅編寫高層的抽象語(yǔ)法規(guī)則就可以了。

編寫語(yǔ)法規(guī)則

下面我們來(lái)編寫一個(gè)柴犬語(yǔ)(匹膽)的語(yǔ)法解析器以便熟悉mpc的用法。

先來(lái)看一下Doge語(yǔ)言的語(yǔ)法描述:

?形容詞(Adjective)包括wow>many>so、such符號(hào)。

?名詞(Noun)包括lisp>language>c、book>build符號(hào)。

?短語(yǔ)(Phrase)由形容詞(Adjective)后接名詞(Noun)組成。

?Doge語(yǔ)言由0到多個(gè)短語(yǔ)(Phrase)組成。

現(xiàn)在我們嘗試定義一下形容詞和名詞,為此我們創(chuàng)建兩個(gè)解析器,類型是mpc_

parser_t*,然后將解析器存儲(chǔ)在Adjective和Noun兩個(gè)變量中。mpc_or函

數(shù)產(chǎn)生一個(gè)解析器,它可接受的語(yǔ)句必須是指定語(yǔ)句中的一個(gè)。而mpc_sym將

字符串轉(zhuǎn)化為一個(gè)語(yǔ)句。

下面的代碼也正如我們上面的描述一樣:

/*Buildaparser'Adjective'torecognizedescriptions*/mpc_parser_t*Adjec

tive=mpc_or(!,mpc_sym(z/wow,z),mpc_sym(z,manyz/),mpc_syin(〃so〃),mpc_sym

("such"));/*Buildaparser'Noun'torecognizethings*/mpcparsert*Nou

n=mpc_or(5,mpc_syni(〃lisp〃),mpc_sym(z,language/z),mpc_sym(/zbookz/),mpc_sy

m(〃build〃),mpc_sym(〃c〃));

我怎樣才能使用上面的這些mpc庫(kù)提供的函數(shù)?

現(xiàn)在先不用擔(dān)心編譯和運(yùn)行程序的事情,先確保理解背后的理論知識(shí)。在下一

章中我們將使用使用mpc實(shí)現(xiàn)一個(gè)更加接近我們的Lisp的語(yǔ)言。

接下來(lái),我們使用已經(jīng)定義好的解析器Adjective、Noun來(lái)定義短語(yǔ)(Phras

e)解析器。mpc_and函數(shù)返回的解析器可接受的語(yǔ)句必須是各個(gè)語(yǔ)句按照順序

出現(xiàn)。所以我們將先前定義的Adjective和Noun傳遞給它,表示形容詞后面

緊跟名詞組成短語(yǔ)。mpcf_strfold和free指定了各個(gè)語(yǔ)句的組織及刪除方式,

我們可以暫時(shí)忽略它們。

mpcparsert*Phrase二mpc_and(2,mpcf_strfold,Adjective,Noun,free);

Doge語(yǔ)言是由0到多個(gè)短語(yǔ)(Phrase)組成的。mpc_many函數(shù)表達(dá)的正是這

種邏輯關(guān)系。同樣的,我們可以暫時(shí)忽略mpcf_strfold參數(shù)。

mpc_parser_t*Doge二mpc_many(mpcf_strfold,Phrase);

上述語(yǔ)句表明Doge可以接受任意多條語(yǔ)句。這也意味著Doge語(yǔ)言是無(wú)窮的。

下面列出了一些符合Doge語(yǔ)法的例子:

〃wowbooksuchlanguagemanylisp"〃socsuchbuildsuchlanguage""manybuild

wowc〃〃〃〃wowlispwowcmanylanguage”〃soc〃

我們可以繼續(xù)使用mpc提供的其他函數(shù),一步一步地編寫能解析更加復(fù)雜的語(yǔ)

法的解析器。相應(yīng)地,隨著復(fù)雜度的增加,代碼的可讀性也會(huì)越來(lái)越差。所以,

這種寫法其實(shí)并不簡(jiǎn)單。mpc還提供了一系列的幫助函數(shù)來(lái)幫助用戶更加簡(jiǎn)單

地完成常見的任務(wù),具體的文檔說(shuō)明可以參見項(xiàng)目主頁(yè)。使用這些函數(shù)能夠更

好更快地構(gòu)建復(fù)雜語(yǔ)言的解析器,并能夠提供更加精細(xì)地控制。

更加自然的語(yǔ)法規(guī)則

mpc允許我們使用一種更加自然的方式來(lái)編寫語(yǔ)法規(guī)則。我們可以將整個(gè)語(yǔ)言

的語(yǔ)法規(guī)則寫在一個(gè)長(zhǎng)字符串中,而不是使用啰嗦難懂的C語(yǔ)句。我們也不再

需要關(guān)心如何使用mpcf_strfold或是free參數(shù)組織或刪除各個(gè)語(yǔ)句。所有

的這些工作都是都是自動(dòng)完成的。

下面,我們使用這個(gè)方法重新編寫了上面實(shí)現(xiàn)過的Doge語(yǔ)言:

mpc_parser_t*Adjective=mpc_new(zzadjective,/);mpc_parser_t*Noun=mpc_

new("noun");mpc_parser_t*Phrase=mpc_new("phrase");mpc_parser_t*Doge

二mpc_new(,zdoge,z);mpca_lang(MPCALANGDEFAULT,

\adjective:\〃wow\〃|\〃many\〃\|\〃so\〃|\〃

such\〃;\noun:\〃lisp\〃|\,zlanguage\,z\

|\〃book\〃|\〃build\〃|\〃c\〃;\phrase:<adjective><noun>;

\doge:〈phrase〉*;\〃,Adjective,Noun,Phra

se,Doge);/*Dosomeparsinghere...*/mpc_cleanup(4,Adjective,Noun,Phr

ase,Doge);

即使你現(xiàn)在暫時(shí)不理解上面的長(zhǎng)字符串的語(yǔ)法規(guī)則,也能明顯地感覺到這個(gè)方

法要比之前的清晰的多。下面就來(lái)具體的學(xué)習(xí)一下其中的某些特殊符號(hào)的意義

及用法。

注意到,現(xiàn)在定義一個(gè)語(yǔ)法規(guī)則分為兩個(gè)步驟:

1.使用mpc_new函數(shù)定義語(yǔ)法規(guī)則的名字。

2.使用mpca」ang函數(shù)具體定義這些語(yǔ)法規(guī)則。

mpca.lang函數(shù)的第一個(gè)參數(shù)是操作標(biāo)記,在這里我們使用默認(rèn)選項(xiàng)。第二個(gè)

參數(shù)是C語(yǔ)言的一個(gè)長(zhǎng)字符串。這個(gè)字符串中定義了具體的語(yǔ)法。它包含一系

列的遞歸規(guī)則。每個(gè)規(guī)則分為兩部分,用冒號(hào):隔開,冒號(hào)左邊是規(guī)則的名

字,右邊是規(guī)則的定義,使用;表示規(guī)則結(jié)束。

定義語(yǔ)法規(guī)則的一些特殊符號(hào)的作用如下:

語(yǔ)法表示作用

〃1〃

ab要求字符串a(chǎn)b

,,

a要求字符a

ab要求先有一個(gè)字符a,后面緊跟一個(gè)字符b

'a'1'b'要求有字符a或字符b

'a,*要求有0個(gè)或多個(gè)字符a

'a'+要求有1個(gè)或多個(gè)字符a

<abba>要求滿足名為abba定義的語(yǔ)法規(guī)則

似曾相識(shí)的感覺?

上面的一些語(yǔ)法規(guī)則有沒有似曾相識(shí)的感覺?你沒有猜錯(cuò),mpca_lang函數(shù)就

是用mpc_many>mpc_and、mpc_or這些函數(shù)來(lái)實(shí)現(xiàn)的,干凈利落,不拖泥帶

水。

根據(jù)表中給出的規(guī)則嘗試著理解一下上面的代碼,看看是不是等價(jià)于之前我們

前面用代碼定義過的語(yǔ)法解析器?

在后面的章節(jié)中,我們會(huì)使用這個(gè)方法來(lái)定義我們的語(yǔ)法規(guī)則。剛開始可能并

不是那么容易理解,但隨著時(shí)間的推移,我們練習(xí)的越來(lái)越多,你也將會(huì)更加

熟悉,并將知道如何創(chuàng)建和編輯自己的語(yǔ)法規(guī)則。

本章更加注重的是理論知識(shí),所以在做彩蛋部分時(shí),不要太在意正確性,思考

實(shí)現(xiàn)的方式才是最重要的。

參考

doge_code.c

ttinclude〃mpc.h〃intmain(intargc,char**argv){/*Buildaparser'Adje

ctive,torecognizedescriptions*/mpc_parser_t*Adjective=mpc_or(4,

mpc_sym

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論