larva 设计思路
接下来,开始动真格的,设计实现一个语言,或者确切说是一个相对实用的语言
虽然已经写了很多的理论相关,不过实际要做的时候,并没必要都用上,因为可以利用已有的东东做基础;同样的,也没有必要实现用户态并发之类的特性,至少在能预见的版本中,我只是需要其计算能力而已。另一个原因是,语言实现是一个很麻烦的事情,假如有一个团队,或可以容忍很长的时间,则可以各方面都做得好些,可惜人力时间都不够,还要做出一个像样的东西,就只能挑重点了,具体地有两个方向: 一、非必要的特性都砍掉,在合理范围内对语法、实现做强约定,比如对象动态增减属性 二、如果一个特性可以通过更通用的特性来实现,就可以不考虑它,比如闭包 用武侠小说的话说,就是“骑最快的马,拿最好的刀,杀最狠的人”,攻其一点,不及其余。当然了,也需要一个平衡,如果一开始只图方便,可能后面要加特性就比较麻烦,开发方面用迭代式的,即快速做出可用的东西,然后逐步修改
因此,像前面说的,采用将语言转换成另一种高级语言的实现思路能方便些,比如我这里选择了java,这样一来有很多细节问题就不用动手处理了(比如虚拟机的实现,很多基础库的实现,垃圾收集等),不过,语言和实现还是需要分开,也就是说,虽然采用转换为java来实现,但语言本身是一个独立的标准,语法不能太依赖于java的特性,否则只能是一个java的简化版了 P.S.理论上可以转化为任意语言,包括直接编译成可执行文件或自己设计字节码等,不过将其转为python之类的可能意义不是很大,因为那样还不如用python直接写代码了,再者,我也只考虑命令式语言,且转化过程要直观方便。所以,虽然原则上语言和实现是要分开的,但实际不会做得那么绝对,或者说,我只考虑将其转成java,C/C++,C#,go这种写起来啰嗦但实用意义较大的,因此语法设计之类也会有意无意地向它们(的共同特性)靠拢
这个语言我取名叫larva,名字来自于星际中zerg的幼虫,有点比喻的意思——它可以变形成各种生物。代码会发布在git.oschina.net上,包括python写的编译器、java实现的内置数据类型和库,以及一些larva自己实现的库,不过目前还是私有项目,没有公开(事实上我还一个字都没写)
关于这个语言,也和前面说的一样,源代码结构、语法、数据类型上仿照python,不过会去掉或修改python的很多(不常用的)特性,另外,为方便起见,有些特性(比如class)并不会在一开始就设计好,而是在后续添加,同样的,代码的效率优化在一开始仅作设计上的准备,也就是说某些设计会为将来考虑,但具体的优化不会现在做
要详细描述一套语言规范是很麻烦的事情,尤其是一堆BNF范式,就算我有心思写估计大家也懒得看,从这个角度说,仿照某某语言来描述就很容易了,只需要重点说明有区别的地方即可,其余皆可触类旁通,因此对于larva(第一版本),我觉得只需要简单说明一些规则:
源代码结构: 一个larva程序可能由若干模块组成,每个模块是一个.lar文件,或环境支持的、符合.lar文件接口的其他形式(如某些内置模块需要用宿主语言实现) 和python不同,每个模块仅包含两种元素:全局变量和函数,且函数名不作为变量使用,其定义不作为赋值语句,可看做是声明;由于是动态类型(见下),全局变量在定义的时候实际是一个赋值语句给其赋予初始值,对每个程序运行实例而言,初始值的初始化在进程启动时统一进行,全局变量的初始化如果出现循环依赖的情况,需要由编译器检查。在同一个模块中,导入模块名、全局变量和函数的名字不能互相冲突(具体冲突规则见下) 程序运行入口是main函数,每个模块都可以有main函数(主要是为了方便测试,就好像python的模块里面可以用if name == "main"一样),但是一个程序在编译的时候需要指定主模块,即启动模块,程序运行将从主模块的main函数开始,如果主模块没有main函数,则编译报错。非主模块的main函数作为其一个普通的导出函数 P.S. 暂不支持模块组成的包,如果代码结构复杂了可以增加,这个是编译期的事情,比较容易解决 不支持动态链接功能,这个其实要做也可以,不过麻烦,而且需要运行时环境支持,放弃
.lar文件结构: .lar文件必须由兼容ASCII的编码编辑而成(编码格式指定语法见下) 语法上讲,.lar文件中分为三类语句: 一、import语句,导入其他模块,格式: import 模块名 import并不是一条可执行语句,仅在编译期起作用,且必须放置在文件开头。导入后,模块名可在当前模块中使用,同一级命名空间中不可出现同名模块、全局变量或函数,次级命名空间中可覆盖此名字(如函数局部变量名) 二、全局变量定义,格式: 全局变量名 = 表达式 三、函数定义,格式: func 函数名(参数列表): 函数体 模块元素引用方式,采用“.”运算符,例如: import a a.b a.f() 同名全局变量不可重复定义,且函数名不可与全局变量冲突 支持函数重载,不同参数类型的函数可同名
语句块和语句: larva的语句块和python一样,采用缩进格式,缩进计算规则同python 语句方面也类似python,只是去掉了“\”结尾的折行方式(感觉比较丑陋),一律采用括号折行,即某一行结束时如果有未匹配完整的括号,则视作折行,下一行的缩进会被忽略 用//做单行注释,/.../做多行注释(#号打算留作另外的用途) 空行或仅含注释的行不计算缩进
关键字: import:导入模块 func:定义函数 true,false:bool值的两个常量 nil:nil常量 if,elif,else:条件选择语句 while:while循环 for:for循环 in:作为操作符时,判断一个对象是否在容器中,在for语句中,解释为对一个对象进行迭代 break,continue:循环控制相关 return:从函数返回 not,and,or:逻辑运算符 global:全局变量声明 print:打印语句 大部分关键字的含义都比较直观,具体到语法分析的时候再逐个讨论
类型系统: 采用动态类型,基本的类型有: int:整数,带符号,限定位数(32或64,可编译期指定) uint:无符号整数,限定位数,位数与int同,不过由于java没有无符号数,暂时不实现 long:长整数,不限位数,用于高精度计算 整数类型和python的一个区别是,int之间的计算如果溢出,不会自动转为long(这是为了后续能对int进行相关的效率优化,且绝大多数程序不需要这个特性) float:双精度浮点数,简单对应C或java的double类型 str:字符串,使用比较通用的16位的unicode字符串 byte:字节串,存储字节流 list:列表,对应python的list dict:hash表字典,对应python的dict tuple:元组,对应python的tuple
常量: 整数常量:支持普通10进制表示,0或0o开头的8进制表示,0b开头的2进制表示,0x开头的十六进制表示 整数常量以无符号标识u或U结尾表示无符号常量 整数常量以十进制形式表示时,若未指定无符号标识,则范围不可超过int,若指定无符号标识,则范围不可超过uint 整数常量以十进制之外的形式表示时,一律以uint格式解析,但解析结果是int类型 长整数常量支持整数常量上述表示形式,以长整数标识L结尾(因为小写的l和1很接近,禁用) 浮点数常量格式与python相同 字符串常量由""或''包含,转义规则和python相同,暂不支持三引号格式和raw串(后面再做) 字符串常量的编码由所处模块的编码标识指定,若解码失败则编译报错 字节串常量由b""或b''包含,转义规则和字符串常量同,禁止出现非ASCII list,dict,tuple的格式均与python同,不过前两者其实算不上常量,顺道一提罢了
模块编码: 模块的编码格式在模块第一行指定,格式:
coding=编码名
模块/函数/变量名:[A-Za-z][A-Za-z0-9]*
运算符和优先级: 这个大体上和python一致,对逻辑运算做了些修改,not,and,or不再具有python中的求值特性,统一计算出bool类型的结果,具体到语法分析再详述
函数: 函数由func语句在模块中定义,局部变量和全局变量的规则和python一致,即局部变量直接赋值即算作定义,全局变量如果只是引用则不用声明,而如果要修改全局变量的值(不是内容),则需要global声明 不支持闭包,不支持可变参数,支持重载 函数的每个局部变量在使用的时候都必须已经有一个值,即不允许在赋值前使用,由编译器做检查 暂时不支持函数作为对象传递、赋值
内置标准化类型和函数: 类型:int,long,float,str,list,dict,tuple 类型使用和python类似,像普通函数一样即可,不过没有python中那么复杂的继承和类型系统 函数:range,... 内置函数应该是很多的,不过我只想到这个,其他的很多都可以用方法来做
内置类型的方法: len:返回对象的长度(对有长度概念的对象) 其他一些对象有自己的各种方法,比如list的append等
大致就这样,其实还是挺模糊的,具体的一些问题,在做的过程中会碰到