学习笔记之编程达到一个高的境界就是自制脚本语言(图)
编程达到一个高的境界就是自制脚本语言,经过这能够精通编程里面的高深的技术,如编译原理、语言处理器、编译器与解释器,这些都是表明一个程序员实力的技术。
每一个程序员都有实现属于本身编程语言的梦想,说其是梦想,缘由是实现的难度很大......这种状况一直持续到《自制编程语言》的出现。
《自制编程语言》郑钢著
本书讲的是纯粹的技术“干货”,符合郑钢老师一向的写做风格,这是他静心写出来的东西,内容满满,很值得阅读。滴滴系统部技术高级总监于晓声说:“很高兴能成为本书的首批读者,也很高兴能为本书写推荐序。”
刚拿到本书手稿时,从书名上我意识到这是对我胃口的书。果真,整书阅读之后,收获颇多。现在程序员的开发成本已经很低了,项目中有各类成熟的框架和库可供选择和使用,但还有人能静下心来研究编译器这么底层的技术,实属可贵。本书犹如一把火炬,点燃了技术人心里对开发的热情。
依稀记得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是修改后的内容。程序员