《两周自制脚本语言》读后感 @阿狸不歌

      乘着春节假期闲来无事,准备挑战一下有点难度的事情,试着写一门脚本语言。没有任何依据,要想凭空造一门脚本语言,那是几乎不可能的事情,所以首先要解决的是参考资料的问题——龙🐲(Compilers: Principles,Techniques,and Tools)、虎🐯(Modern Compiler Implementation in C)、鲸🐳(Advanced Compiler Design and Implementation)三书是编译原理的三大经典,可是看看这几本书的个头以及有限的假期,看来只能等以后再来啃他们了。到亚马逊和京东上搜索“自制语言”,出来的结果全是图灵出的:《两周自制脚本语言》、《自制编程语言》、《自制编译器》,而且与设计脚本语言有关的中文书似乎只此一本——《两周自制脚本语言

英文书里倒是也有一本2016年新出的关于自制脚本语言的书《Writing An Interpreter In Go》,粗看了一下觉得不错,也列为参考资料。


选好参考资料,以两周做为时间参考坐标,我的自制脚本之旅就开始启动了:

第一天:来,我们一起做些什么吧 

      翻开之后,发现第一天的内容好像少的让我有些吃惊,只有9页,真的要花一天才能读完吗😓❓ 当然内容虽少却是提纲挈领的一章,里面介绍了要设计一门脚本语言必备的基础概念:什么是机器语言、什么是汇编语言、解释器和编译器有什么区别等等……最重要的是介绍了我们要设计一门编程语言必不可少的结构—— 如下图所示:

首先是源代码,程序员每天都要打交道的东西。既然是“自制”,那么现在这个语言要靠自己设计了,这是语言设计师的天地,语法(风格)任由我来设定,比如:

                      令 年龄 = 1;

                      令 名字 = "我的姓名";

                      令 运算结果 = 10 乘以 (20除以2);

/*上面这三行看上去虽然和程序员们日常打交道的各种语言不那么一样,但是应该还是能看懂的吧😊*/

第二步,是词法分析(Lexing),我们需要实现一个词法分析器(被称为lexical analyzer 或 lexer  也有叫scanner的),把上面这些语句进一步拆分成特定的“符号”,比如把“令 年龄 = 1;” 拆分为“令”、“年龄”、“=”、“1”、“;”,这个拆分的结果被称为Token。

第三步,是语法分析(Parsing),我们需要实现一个语法分析器(即Parser),语法分析器的作用就是把前面一步词法分析(Lexing)获取的Token进一步的解析并生成抽象语法树(abstract syntax tree或者缩写为AST),这是每个编程语言(不论是解释型的还是编译型的)都少不了的部分。

第四步,则是脚本语言与编译型语言的分野处,通过Parser拿到抽象语法树(AST)后,如果你要通过编译器(Compiler)生成机器码后再运行,那么你搞的就是编译型语言,如果你是要通过解释器(Interpreter)直接执行,那么你搞的就是脚本语言。如果我既搞了编译器,又搞了解释器,那算什么呢 —— 其实这真的没有问题,只要你手上已经有了抽象语法树(AST),那你可以同时把两者都给实现了😄。

第一章虽然只有短短9页,但是把整本书的内容做了高度的概括,并且在第7-9页列出了按天(章)划分的学习计划,剧透一下,其实本书要完整看完并不是14天,最后还有5章(天)是用于自学的,按照作者建议的阅读顺序是:先读完第 2~8 章,之后是第 15、16 章,若还有时间,再读第 11 章和第 13 章。


第一步:设计语言风格

既然是自制脚本语言,那么我并不打算完整的跟着本书作者后面亦步亦趋,而是从一开始就确立了本次实战的语言风格:中文型——除了通用操作符(+、-、*、/、=、;等)之外,关键字都用中文😈:

变量赋值在上面已经举例了,下面就举一些例子:


流程控制语句:

若(年龄>18){

     打印("年龄已经符合要求");

}否则{

    打印("岁数还小");

}


循环语句1:

令 总数 = 0;

当(总数<1000){

       打印(总数);

       总数++;

}


循环语句2:

令 数组 = [1,2,3,4,5];

在 数组 遍历 i,v {

      打印("第" + i + "个数组元素是:" + v);   

}


第二步:设计词法分析器(Lexing)

      在确定了语言风格之后,其实词法分析器就不是很困难了,说起来就是把每一行里哪些是关键字(比如“令”、“遍历”、“在”、“当”、“若”、“否则”)、哪些是变量(如“i”、“v”、“年龄”等)、哪些是操作符(如“+”、“-”、“*"、“/”、“=”)、哪些是分隔符(如“{”、“}”、“;”等)并且分门别类,这些被分析出来的元素统称为Token。

至于如何找出Token,方法其实并不限定,这取决于第一步你的语言风格是如何设计的,常见的分析方法:(1)用正则表达式分析,本书第三章用的是这个方法。

(2)正则表达式能够表述非常复杂的字符串模式匹配逻辑,但它并非万能,所以本书又在第十五章提供了一个“手工词法分析器”的方法,将正则表达式分析方法改写为自动机方法,具体流程见下图



第三步:设计语法分析器(Parsing)

      我们在第二步获得Token之后,就要通过语法分析器(Parser)将Token解析为抽象语法树(abstract syntax tree,即AST),如果学过编译原理,那么你不会对AST感到陌生,如果不熟悉的话,应该好好阅读一下第4章:用于表示程序的对象。

      在计算机科学中,抽象语法树,是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。树上的每个节点都表示源代码中的一种结构。之所以说语法是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节。比如,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现;而类似于if-condition-then这样的条件跳转语句,可以使用带有两个分支的节点来表示。

例如我们要用一个抽象语法树表示一个表达式: 13+x*2

会得到下面的🌲树状图:


        了解完抽象语法树的概念后,我们将正式开始语法分析器的设计。在第二步,我们已经通过词法分析器(Lexer)分解为了单词序列。而语法分析器(Parser)的任务是将这些单词序列与语法规则定义的模式进行匹配,并构造出如上图那样的抽象语法树。

       在语法设计的时候,你设计的表达式越复杂,则在语法分析的时候,面临的困难就越大,所以从语言使用者的角度来看,我还是喜欢设计简洁的语言。

      其实对于搞出语法分析器还有另一种办法,那就是借助语法分析器的生成器(Parser Generator),例如yacc, bison 或者 ANTLR,这些都是比较完善的生成器,可以大大降低工作量以及减少bug。

      当然本着我们要自己搞一套的理念😄,本书中仍然提供了手写语法分析器的过程以及代码。自己手写语法分析器的过程,当然是对语法分析的一次极有价值的实践,所以嘛,从玩票的角度,我们仍然应该坚持手写Parser💪🏽。


第四步:设计解释器并执行程序(Evaluation)

        如第一天教程所介绍的,我们拿到抽象语法树(AST)之后,要编译还是解释就取决于我们的需求了,当然,有兴趣的话两者都可以实现,编译器的设计请参看图灵的自制编译器一书。

      言归正传,从抽象语法树(AST)到通过求值(Evaluate)得到结果,其实有很多种策略,根据策略的不同,执行效率也会有较大的差别,最简单的策略就是本书中提到的遍历抽象语法树解释法(A Tree-Walking Interpreter),即从抽象语法树的根节点开始遍历该树直至叶节点,并计算各节点的内容。

      我们的语法树的每个节点都需要具备相应的求值(eval)方法。eval方法将计算与以该节点为根的子树对应的语句、表达式及子表达式,并返回执行结果。因此,只要调用抽象语法树的根节点对象的eval方法,就能完整执行该语法树对应的程序。eval方法将递归调用该节点的子节点的eval方法,并根据它们的返回值计算自身的返回值,最后将结果返回给调用者。


遍历抽象语法树的节点

    说到关键之处,本书作者引入了一个其自己开发的“GlounJ”的系统来实现解释器。当然,我们也完全可以抛开“GlounJ”系统来完成自己的解释器。


总结:

      本书到第6章,其实已经把一个初步的脚本语言设计完成,并且涉及到了开发一门脚本语言所需要的基础知识和概念,后面的7-10章则陆续介绍了如何引入函数、面向对象、数组的方法,进一步的丰富了脚本语言,使其具有图灵完备性。第11-14章则进一步讨论如何优化解释器的性能:优化变量读写性能、优化对象操作性能、设计中间代码解释器、添加静态类型等等。第15-19章则从另一种角度出发,以不同的方式实现了脚本语言的第2、3、4步,反映了解决问题的不同思路。 

     总的来说,如果你要是想设计并实现一门脚本语言,而又需要中文教材的话,本书是你目前的唯一选择🤖。至于缺点嘛,我觉得就是大段的代码边上没有注释,啃起来有点费劲,要是能有一个带注释的版本会更好!

    最后,感谢图灵带给我们的这本好书,下一次,我将挑战自制编程语言自制编译器😎

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容