有没有感觉设计一门语言实在是太有意思了,可以自定义语法规则,我的“地盘听我的”。
脚本语言的功能
本书设计一门纯粹的面向对象脚本语言,任何语言都有个名词,这里给这个语言起个名字——sparrow(麻雀)。它支持的功能如下。
1 变量
支持局部变量和局部变量的定义。
变量可引用、赋值。
内部复合数据类型以大写字符开头,如System.print()
2 基本数据类型
数值:包括整数和浮点数。
字符串:包括普通字符和unicode。
list:列表,如Python中的list。
支持字面量创建,如['a', 'b']和new方法创建。
元素通过下标list'[索引]'获得。
map:哈希数组,如Python中的字典。支持字面量创建和new创建。字面量创建如:
{
'k1':v1
"k2":v2
}
key可以是任何数据类型。同样支持new方法创建。
value通过下标map'[key]'获得。
range:用以确定一段整数范围,用符号..表示。range包括from和to两个成员,分别表示这段范围的起和始,用区间表示[from, to],即包括from和to。如range“2..6”,2就是from,6就是to,“2..6”表示2、3、4、5、6。“..”类似于Python中的分片操作符“:”,只不过我们包括了结尾的to,而Python不包括,若用区间表示则to后面的是右小括号“)”。
3 运算
数值:+、−、*、/、%。
逻辑:>、<、==、!=、||、&&、?:、|、&等。
位运算:>>、<<。
方法调用:.。
索引:[]。
字符串:+、%(字符串内的表达式)
4 控制结构
支持if-else选择。
支持while循环。
支持for循环。
支持break退出循环。
支持continue,跳过本次循环体后面的部分,继续下一轮循环。
支持return返回。
5 函数
尽管这是一门面向对象语言,但也支持传统意义上的函数,用关键字fun实现函数定义。
函数也是用类实现。
支持函数重载。
6 类
就是传统意义上的class,包括类定义和类实例,静态类。
实现继承,所有类都是object类的子类。
类成员(也称域,或字段)必须先声明再引用。
方法包括method、getter、setter、subscript、subscriptSetter和构造函数。支持块参数,块参数的参数是用“||”括起来的参数列表,以逗号分隔。
支持静态方法。
7 线程
支持线程创建及调度。
8 模块
支持执行模块和模块内模块变量的单独导入。
9 注释
行注释://
块注释:/* 块注释 */
以上列举若有遗漏则以实际代码为主。
关键字
有以下关键字被提前征用了。
var:用于变量定义。
fun:用于函数定义。
if:用于条件判断。
else:用于条件判断的else分支。
true:bool值真。
false:bool值假。
while:用于while循环。
for:用于for循环。
break:用于退出循环。
continue:用于结束本次循环并进入下一轮循环。
return:用于从函数返回。
null:空值。
class:用于类定义。
is:用于判断类是否为某类的子类,即“is a”。
static:用于设置静态文法。
this:用于指向本实例。
super:用于指向父类。
import:用于导入模块。
脚本的执行方式
我们采用传统的虚拟机作为执行方式,即要实现一个虚拟机。编译器先把源码编译为opcode,再让虚拟机执行opcode。
opcode即操作码,是自定义的一套专供虚拟机执行的指令,后面我们在实现虚拟机时会详细介绍。
“纯手工”的开发环境
既然本着教学的目的我觉得应该拿出教学的诚意,因此这里所说的“纯手工”是指编码中不想借助STL或其他类似的泛型语言,没有第三方库,一切以最基础最原始的形式展现语言的奥秘,因此选择了C语言,确定地说是C89,并不是较新的C99标准,诚意满满,让我们纯手工去编码吧。
基础开发环境是:
宿主系统是Linux,采用CentOS release 6.8 (Final);
编译器是gcc,版本是gcc version 4.4.7 20120313 (Red Hat 4.4.7-17) (GCC)。
定义sparrow语言的文法
在之前介绍的文法中,我们采用的是大写字母表示非终结符,小写字母表示终结符,然而我们也说过了,在现实中为了便于编程,一般都用正规文法来定义语言,正规文法说白了就是用正则表达式定义的文法,因此本小节的基础是正则表达式,为保证容易看懂,我会用最简单的方式书写正则表达式。
值得注意的是,正规文法与第0章中介绍的文法有很大的差别,主要是涉及终结符是用''表示,原因是正规文法中会涉及()、[]和{},这些在正规文法中都是元字符,有其特殊含义:()表示成为一组,[]表示范围,{}表示重复。
但在实际语言它们只是字符串字面量(即终结符),比如在语言中()表示函数名后面的括号,也可表示表达式中的小括号,[]表示下标索引,因此为避免冲突,正规文法中用单引号括起的是终结符。
其实语法和传统语言差不多,只是用文法来描述就显得生涩了。注意,正规文法中的[]与EBNF中的意义不同,在此表示范围,其中可以用-表示一段连续的范围,比如0-9就表示0至9之间(包括0和9)的任何数;a-z同理,表示字母a到字母z之间的任何字母。[]后面一般会接量词,当然量词不一定只用在[]之后,但它一定是用在某个字符之后以表示该字符的数量,其前不能没有字符。
按照数量级别划分有3种量词,+表示重复出现1次以上,*表示重复出现0次以上,?表示出现0次或1次,比如可用[\t]*表示0个或多个空白字符,其中\t是tab。
注意此处[]中的空白是空格,为了突显这里有个空格就写了两个。.表示任意字符,包括控制字符比如回车等,|表示或者、任意其一,比如a|b,表示a和b两者取其一,注意,|是对两边的整体有效,并不是只对紧邻|的有效。
比如对于ab|cd的意思是ab或者cd,如果想表达abd或acd,可以用分组符号(),就是小括号对儿。()表示作为一组考虑,使相应的正则符号应用于整个组成员。
初次接触文法的读者可能对递归定义感到“消化不良”,比如非终结符exp是用于定义表达式,exp是由infixExp等非终结符组成,而infixExp又是由exp组成,看上去有点死循环出不来了,但你不要忘了,infixExp只是exp的其中一个组成部分,exp还可以由num、id等指代,num和id下面再无递归定义,这就是递归终止的条件。因此infixExp的组成部分exp也会是num或id等。
下面是具体的样本。
以下是上述文件的执行结果,其中的spr是最终的脚本解释器(包括编译器及虚拟机),spr是sparrow的缩写。
以上./spr manager.sp就是执行脚本文件manager.sp,这与任何脚本语言的运行方法都是一致的,执行过就是脚本的输出,大家有兴趣可以核对一下结果,除了System.clock返回的时间戳是动态变化的外,其他不变。
郑钢 著
本书全面从脚本语言和虚拟机介绍开始,讲解了词法分析的实现、一些底层数据结构的实现、符号表及类的结构符号表,常量存储,局部变量,模块变量,方法存储、虚拟机原理、运行时栈实现、编译的实现、语法分析和语法制导自顶向下算符优先构造规则、调试、查看指令流、查看运行时栈、给类添加更多的方法、垃圾回收实现、添加命令行支持命令行接口。
小福利
你认为程序员存在的最大价值是什么,参与话题有机会获奖哦。截止时间9月7日17时,留言+转发本活动到朋友圈,小编将抽奖选出3名读者赠送纸书1本。
扫码关注我们
点击阅读原文,直接购买《自制编程语言》
阅读原文