前言我在之前的文章中多次说过,大学生在校期间要掌握基础知识,因为大学生没有工作压力,时间也很多。基础知识比较枯燥。如果想有成就感,可以做一些简单的系统,比如简单的编程语言,基本功能的OS……杨涛是我知识星球“码农出海”的大学生,还有他在星球上提到他做了一个Simpleprogramminglanguageinterpreter,我建议他把过程写出来,就是这篇文章。下面的“我”就是杨涛。为什么要自己写解释器?从大学开始学习编程已经快两年了,接触了很多编程语言。一开始,我学的是C语言;后来想写Android软件,学了Java;然后接触了后台开发,学习了Python;后来陆续接触到Go、Dart、C++。仔细一算,我已经接触了6种语言!但是仔细一想,我好像什么也没学到。过年回家遇到一个四年级的孩子,对电脑很感兴趣(ps:这孩子会写一点python,html,现在的孩子太强了),问我这样貌似计算机上的程序如何运行,代码如何运行等基本问题,可悲的是,这些问题我听不懂,一知半解,无法向孩子解释清楚。说来惭愧,我是学计算机的,连这些最基本的问题都没弄明白。这是我深入学习编译原理、计算机组成原理和操作系统基础知识的重要原因。学习编译原理的最简单方法(对我而言)可能是自己实现一种编程语言。虽然费时费力,但你可以清楚地了解整个过程。另一个重要的原因是有一种想自己写一门语言的冲动。尤其是学了这么多语言之后,这个念头就会生出来。不同的语言有不同的特点让我喜欢:Python语法优美简洁,静态语言Go像动态语言一样实现了duck类型的接口,Dart有很多语法糖和方便的异步...(当然,这些特征各不相同,智者见智)。但是如果你找不到一种语言具有你喜欢的所有特性,那就自己写一个,你可以添加所有你喜欢的特性。学习过程学校的汇编原理课安排在大三,我还没学,所以一切从0开始。我先看了前桥和美写的《自制编程语言》这本书。本书非常实用,没有介绍过多的理论知识,而是直接教你如何编写编程语言。从这本书中,我了解了编写一门编程语言的大致流程和大致思路。但是书中的很多解释并不充分,对于没有接触过编译技术的人来说还是有点迷惑(也可能是我自己理解的不够好)。我也是在了解了大概的思路后开始尝试自己写,然后回过头去看书和作者提供的源码,更好的理解作者在做什么。还有一些书介绍的不够,比如yacc和lex的使用。毕竟这种工具比较流行。可以在网上多找别人写的博客,多尝试就能很好的掌握。因为我更喜欢在实战中学习,前期只是学习了大概的思路,并没有深入的理论知识,所以直接开始了coding。如果在执行过程中遇到问题,多看看书或者上网搜索答案。设计与实现我选择为动态语言编写解释器,而不是为静态语言编写编译器。我之所以要写解释器,并不是因为我更喜欢动态语言。事实上,我更喜欢静态语言。真正的原因是我认为这只是第一次尝试。很多东西不懂,肯定会写不好。为什么不先写一个动态语言,等我真的学得更好了再回来写一个新的。您选择的语言。在我正式开始写代码之前,我还需要给这门语言起个名字。虽然只是一个实践项目,但还是需要一个名字的。选择一个名字并不是一件容易的事,就像为一个函数或类选择一个合适的名字一样。听说合适的函数名或类名也能反映出整个项目的设计是否合理合乎逻辑,但是语言的名字好像没有这样的意思。第一个跳进我脑海里的名字是仙人掌(cactus,仙人掌)。很喜欢这个名字,所以暂时把Cactus留作自己想写的静态语言(希望自己真的能写出来,不要白留)。仙人球是植物(碰巧是静止的),同样仙人球是刺猬。动物是动态的,正好符合我要写的动态语言,所以叫Hedgehog。前面提到了lex和yacc,在我写的编程语言中也使用这两个工具进行词法分析和语法分析。既然要自己写一门语言,为什么还要借助其他工具呢?当然,我不能以“不重复造轮子”为借口。我想写自己的编程语言只是为了造轮子。真正的目的是简单。如前所述,我将此视为一个动手项目。为了熟悉整个过程,我在整个过程中以简单为原则。我在很多地方都能想到更好但更复杂的实现方式,但大部分还是用最好的方式简单是保持整个项目逻辑清晰的最好方式。我更多的目的是为了了解整体的流程和整体的结构,所以尽量让部分保持简单(当然懒也是一个重要原因)。当然,后续的词法分析和语法分析肯定要自己去实现了。毕竟这是编译器或者解释器的前端,也是非常重要的。解释器是用C语言写的。以前从来没有用C语言写过这么大的项目(虽然目前只有2000多行代码),这次也学到了很多C语言的高级用法。例如:void(*func)(void)是一个函数指针,返回值为空,参数为空;void(*signal(intsigno,void(*func)(int)))(int);是一个返回值Function指针,一个参数为(intsigno,void(*func)(int)),(anint,afunctionpointer)的函数,其中函数名为signal。我之所以用函数指针,是为了用C语言写面向对象的。一开始完全是面向过程的,简单的通过不同的文件实现简单的封装。后来随着写的越来越大,出现了各种问题,比如头文件的交叉引用导致编译器报错。还有很多地方可以用面向对象更好的实现,比如表达式的创建和求值。如果有表达式接口,就可以利用多态的好处,不需要写一个庞大的switch。case语句使用枚举来判断不同的表达式,调用不同的函数。听说制约程序员的不是编程语言,而是编程思维。当然C语言也可以面向对象写,数据可以封装在结构体中,然后在结构体中加上函数指针,实现类的方法。也可以通过自己实现虚函数表,在对象初始化时将函数指针指向不同的函数来实现多态。大多数面向对象的特性都有相应的方法实现,但语法不像原生的面向对象语言那么简单。还有一些与语言本身设计相关的问题:(1)for、if等语句中变量的作用域。一开始我设计的是Java,和C++一样。for和if声明在代码块变量中,其作用域只存在于整个代码块中。后来发现这是一个弱类型的动态语言,独立的运行环境并没有什么特别的用处,于是改成了和Python一样:这种代码块没有独立的运行环境。(2)将函数视为什么的问题。例如在Java这种纯面向对象的语言中,函数只能是对象的方法。这里我把函数看作是一种基本的数据类型,和字符串一样,可以直接用于参数传递和赋值。毕竟这是你自己的编程语言,你可以随心所欲地设计,所以大部分的设计都是基于自己的想法,你认为合理的就可以了(当然不是随意设计,但要根据自己的情况)实际经验来选择合理的设计)。当然,在我第一次写编程语言的时候,有很多地方是不知道如何合理设计的。这时候我参考了自己学过的编程语言,思考它为什么采用这样的设计,有哪些考虑。这种思考让我对之前学过的编程语言有了更深刻的理解,可以说是受益匪浅。我逐渐意识到,编程语言的设计往往是设计者编程思维的体现。简介Hedgehog说了这么多,是时候简单介绍一下我写的编程语言了。目前还是很粗糙,以后慢慢完善。hedgehog的大部分设计与python类似,不需要声明变量类型,if和for等语句也没有块级作用域。语法有点像go语言:if,for后面不需要for(),但是后面的代码块必须加上{};没有while,但是有forcondition{}代替。但是行尾必须加;这与go不同。大多数设计都是为了简化实现,例如,必须添加{},;是为了简化语法的解析。数据类型a=10;//intb=3.14;//floatc=true;//booleand=null;//nulls="Hello,World!";//字符串控制语句a=10;ifa>10{//`()`isnotnecessary.b=a+20;}elsifa==10{b=a+10;}else{b=a-10;}print(b);loopfori=0;i<10;i=i+1{print(i);ifi>=4{break;}}i=0;fori<10{ifi<5{continue;}print(i);}函数也被看成是一个值(基本数据类型),但目前还没有对其实现垃圾回收,因此直接赋值或其他操作会导致内存错误。//模仿函数funcfbi(n){a,b=0,1;fora
