脚本语言:Xmas(一)
很偶然的一个想法,在从北京回成都的高铁上:我想要一个计算器。于是在火车上花了十来个小时,完成了一个模型:能够处理+-*/的优先级,以及“()”,比如:1+(3+2)*4。这已是一年前的故事了;之后支持了函数,便成为了一门语言(虽然是脚本)。先后经过数次根本性地修改,便成为了现在本文的主角:一个基于栈的,支持过程式、函数式、面向对象三种风格的,拥有垃圾收集器的编译型脚本语言——Xmas。
一、Xmas是什么?
首先她是“圣诞节”的简称,用以纪念多年前,还在看凉宫春日的忧郁时的自己(也是通过其,我第一次知道Xmas,凉宫曾将其画在活动室的窗户上)。
起先,她只是一颗可以执行的抽象语法树,并且没有自己的完整的运行时,很多功能都依赖于宿主系统(C++);后来,她有了自己的字节码,便得以从宿主系统独立出来,编译器、虚拟机、垃圾收集器是各自独立的系统;现在,加入了lambda、面向对象后,一堆语法糖接踵而至,也有了一个更加高效和健壮的GC。
究其根本,其仅仅由以下单元命令组成(以后会大量用到):
push ->入栈 pop ->出栈(实际中并没有这个操作) set ->修改值(参数、变量) +-*/% ->操作符 call ->函数调用 ret ->函数返回 goto ->无条件跳转(如break) jump ->有条件跳转(如if、while)
其中【pop】并不实际存在;因为很多命令本身需要通过pop来获取所操作的内容(基于栈的语言),所以其是集成到其他操作本身的,如:
, $) #res
其表示了完整的减法指令:栈第二个弹出的值($1)作为被减数,第一个弹出的值($0)作为减数,并将结果入栈(#res);其中$代表了出栈,#代表了入栈,res委婉地表示自己是结果(result)。
也就是:1 - 2,是以下指令:
: : : , $) #res
首先将1入栈,再将2入栈;所以,在栈中的顺序自然是:2在栈顶、1在栈的第二个位置(相对栈顶)。
有了以上知识,后面的内容展开才能够更加容易(我使用了生成的指令,来说明代码的语义)。
二、基本类型
Xmas中并没有多少类型;尤其是基本类型:Null、Boolean、String、Ref、Function、Integer、Float、Decimal、Array、List、Set、Map、Object,其实挺多的;作为脚本,一个动态类型的语言,通过库的方式提供复杂的数据结构,是不现实的(太慢);所以,我将所有可能用到的容器类型都作为语言本身的基本类型提供:
1、Array,映射了C++STL的std::vector;当然,在Xmas中可以happy地作为数组使用,还是动态增长的(所以,Xmas中并没有【数组】这个东西)。
2、List,同样映射了std::list;任意节点地删除、插入,是其最完美地使用方法。
3、Set,为了避免出现Map<value, null>这种尴尬的情形(为了完成序列的唯一性),所引入的std::unordered_set。
4、Map,这个是必不可少的std::unordered_map;其原则上可以模拟上面的Array(Map<Integer, value>)、Set。
当然,那个Object是为了支持对象而存在的,后来也顺便支持了面向对象(对象和面向对象,是两个不同的概念)。
Null类型只有一个实例:null;很自然地,其代表了“这里什么也没有”(严格的,所以并没有【undefined】这个多余的东西);Boolean和String,不需多说,唯一需要说明的是:Xmas并没字符类型,只有字符串。
Ref,是一个极为特殊的类型;其是用来和宿主语言交互的,其本身并不代表任何Xmas的事物;它是宿主语言中的东西,专业点就是,其内部包含了一个【非托管对象】;任何需要的,不方便在Xmas中实现的东西(如宿主语言已有,或为了性能),通过Ref便能够加入到Xmas中,当然操作它,亦需要定义适配API,以在Xmas中调用操作Ref中的内容。
Function,就是函数本身(或者说函数指针);为了支持函数式编程风格,函数成为第一值类的公民,是必须的。
Integer和Float,仅仅是long long和double的映射(在C++中);为了支持动态类型,而付出的带价。
Decimal,是本人所写的H.Numeric.BigDecimal.decimal高精度类型的映射版本;许多动态语言都支持高精度类型,Xmas没有理由不支持。
这些类型的目的无碍乎只有一个:严重地拖慢Xmas的运行速度......(玩笑~~)在Xmas第一个简易版本中(就是前面的那个计算器),通过使用模板,其速度比动态类型版本,要快一个数量级。但是,那时Xmas运行时,只能有一种类型.....
三、函数
函数是Xmas第一个支持的模型,也是最早支持的,当然而是作为一门语言的标志和基石;到现在为止,许多特性都是通过原生的函数调用来实现的,而你看到的只是编译器所提供的语法糖(当然,语义本身是有改变的)。
其通过使用和javascript同样的关键词来声明(话说,就语法结构而言,Xmas和javascript几乎一样):
function add(a) { return function(b){ return a + b; }; }
上面便是一个最基本的函数式的函数:其传入被加数a,并返回一个函数,其期待加数b以完成加法操作。嗯,这也是一个闭包(我不喜欢这个错误的说法),或者说一个lambda;其中a便是被闭包所捕获的自由变量。
这并没有什么特别的,特别的是其所产生的指令:
add(a) : : push Function : lambda : : push Function : lambda : , paramNum : , self : false) #res :
add函数在返回之前,居然产生了一次函数调用!!其对应的真实代码是:
function add(a) { return lambda(lambda_0, a); }
其中【lambda_0】便是“function(b){ return a + b;}”的函数本体;而【lambda】本身是一个函数:其用以生成一个闭包(lambda)。到这里一切很明了:Xmas本身并没有传统意义上的闭包,其是通过绑定来模拟实现的(最初的闭包版本,是通过调用bind函数,来实现的)。
那么什么是【传统意义上的闭包】?
1、首先,每个函数都有一个运行时环境;函数运行时的所有参数和本地变量,保存在其中。
2、其次,变量定位是通过这个环境链来查找的;如果当前函数环境没有该变量,便向调用链的上一层寻找。
然而,Xmas并没有这样的环境;个人认为每次函数调用都创建一个环境消耗太大(其实是太懒,不想修改虚拟机),便在编译期创建对应的环境(呃....这个其实,更加复杂;需要类似静态分析的技术),当在当前函数环境(编译期的)没有找到变量定义时,便向上一层寻找。
所以,这更加印证了Xmas其实是一门编译型的脚本:在运行时,一切都是在编译期固定的,不可改变。(当然,通过在运行时,调用一次编译器,便可以修改整个代码的运行时环境;也就是所谓的“动态”。)
四、对象
注意,并不是面向对象.....
对象是有必要存在的;因为Xmas并没有可以自定义结构的方法(无论如何都不可以)。所以,为了支持以下,看上去很方便的方式:
function change(obj) { obj.value = ‘hehe'; }
那个对象的【.】是有必要支持的;否则你没有任何办法通过函数改变一个对象的值(因为Xmas也是传值调用);当然,你可以返回一个全新的对象,但这样不就是太函数式了不是吗?Xmas只是支持函数式风格,并不是说一定要用。
所以,如果一旦这样使用:
function AClass() { obj = new Object(expand()); obj.value = ; obj.getValue = function(){ return this.value;}; obj.setValue = function(value){ return this.value = value;}; return obj; }
你也就可以创建一个对象了:其有字段、也有成员函数。就差继承了.....所以,这依旧只是对象而非面向对象。需要说一下的是,【expand】是一个系统函数,其用来展开参数:可以展开当前函数传入的所有/部分参数(即使是没有声明的)、也可以展开一个Array以作为当次函数调用的参数。这里可以顺便说一句——Xmas是默认支持可变参数的,注意是【默认】;也就是不需要任何额外的声明什么的(是不是很方便?)。
当然获取匿名参数也很简单(直接传递给下一个函数,便是上面的方法):
function add() { ) + ); }
回到对象上的话题,那么,字段的访问,其实现如何?
obj.value = ; obj.func('param');
对应了如下指令:
: : : .value, $) : push String : param : : , $.func) : , paramNum : , self : true)
你可能注意到了那个【self】,其对应了面向对象的this(在Xmas中self和this是对等的,都可以使用);而且,注意到了吗?生成的指令已经知道了该次函数调用,是传入了【self】;这再次印证了那句话:Xmas是一个编译型脚本。
最后说一句,Xmas是支持Object定义的(语法糖而已):
function AClass() { return new Object{ value : ; func : function(){ return "I'm a func";}; }; }
那么,其真实的代码呢?
function AClass() { , 'func', function(){ return "I'm a func";});//Xmas同时支持两种字符串的声明【’/“】 }
【Object】本身依然是一个函数,而已。
五、面向对象
篇幅不多的,先上一段代码:
class Xmas{ function Xmas(){} function toString(){ return this.name(); } function finalize(){} function clone(){ return clone(this); } function name(){ return this.__name; } function has(name){ return has_value(this, name); } function has_func(name){ return typeof(get_value(this, name)) == Function; } }
这是Xmas面向对象时的基类(暂定);其有着几个令人着迷的地方:finalize、clone、__name,还有两个没有显示出的【__type、__final】。
其中所有的函数,以及那三个带“__”的字段;其实都位于this.class.XXX;是的每个class都有一个【class】成员,其是所有实例所共享的;类似于C++的虚函数表。
六、异常
Xmas并不支持异常处理;但她实实在在地回在任何可能的地方抛异常:虚拟机发现函数调用所传入的函数并不是真是函数时、传入参数太少、错误的类型操作、调用宿主函数等等。
原因嘛,是因为在意识到这个问题时;Xmas并不支持面向对象,也就是并没有Exception这个定义;所以抛出的异常,并非Xmas本身所能够识别的类型(是宿主语言的异常)。后来,有了面向对象,但我懒。
但是,Xmas是支持基本的异常处理的:
function throw() { obj = new Object{ finalize : function(){ print("I'm dead");}; }; obj:{ a = + 'hh';//这里会抛异常,Xmas是不允许Integer和String进行“+” } }
其中【:{....}】语法,便是指:无论如何,obj的finalize函数都将被调用。这样对于稀缺的资源(如file、socket)在Xmas中有了可靠的释放保证。
下面便是其对应的指令:
: push String : finalize : push Function : lambda : push Function : Object : , paramNum : , self : false) #res : , $) : finalizer begin//finalize block 开始 : : push String : hh : , $) #res : , $) : finalizer entry//finalizer 入口 : : , $.finalize) : , paramNum : , self : true) : finalizer end//finalize block 结束 : ret
所以,还是字节码来的清晰;finalizer是语法糖和虚拟机同时支持的特性,也是费了自己一些些时间。
当然,finalize函数自己再度抛出异常,并不会影响上一层的finalizer;只是原本的异常信息会被新的异常所覆盖;所打印出的调用堆栈,也将随之改变。
七、还有很多
Xmas是一门有完整的虚拟机支持的脚本;所以她也有一个垃圾收集器:一个基于标记的分代式GC。最初的是一个基于引用计数的简单的收集方案;但无赖,有一天我发现了【环引用】,所以被迫放弃的这个简单直接的方案;接着经过一个星期的努力,第一个基于节点复制算法的分代式GC,完成了。
可惜的是,节点复制算法,需要迁移内存;其对于C++这样的语言,需要太多的控制策略才能够无障碍地使用(主要是函数调用栈和寄存器上的指针地址,需要修改);Xmas只能够在独立的线程中才能够安全的运行,线程间的数据交互需要通过第三个堆(全局堆)。
所以,第三个基于标记的GC,并不需要迁移内存;所以,其并不需要那么多的系统级支持;依然能够狠开心在多线程间运行。
其次,Xmas是默认支持尾递归调用优化;意味着,即使你不希望被优化,也毫无办法。所以,任何尾调用,都将变成迭代;值得一提的是,这个操作是在运行时,由虚拟机完成的,而非编译期;但其性能竟然和普通的循环一样(我表示很惊讶)。
最后,本文,并非结束;后面的系列将针对上面的每个话题;额外地展开,所以,就这样了.......
脚本语言:Xmas(一)的更多相关文章
- 脚本语言:Xmas(二)
本篇,来谈谈类型系统,以及部分与垃圾收集器相关的内容. 一.基本类型 Xmas的基本类型:Null.Boolean.Label.String.Ref.Function.Integer.Float.De ...
- 脚本语言:Xmas(三)
自从将Xmas的GC换成现在的非迁移式的全局收集器后,最近几个月一直耗在Xmas上面:最明显的改变就是:更彻底地支持了面向对象.更强大的编译器. 所以,本文就来说说,真正的Xmas. 一.目标 一门语 ...
- InstallShield 脚本语言学习笔记
InstallShield脚本语言是类似C语言,利用InstallShield的向导或模板都可以生成基本的脚本程序框架,可以在此基础上按自己的意愿进行修改和添加. 一.基本语法规则 ...
- JS脚本语言是什么意思?
javascript,Javascript是一种浏览器端的脚本语言,用来在网页客户端处理与用户的交互,以及实现页面特效.比如提交表单前先验证数据合法性,减少服务器错误和压力.根据客户操作,给出一些提升 ...
- 使用Lua脚本语言开发出高扩展性的系统,AgileEAS.NET SOA中间件Lua脚本引擎介绍
一.前言 AgileEAS.NET SOA 中间件平台是一款基于基于敏捷并行开发思想和Microsoft .Net构件(组件)开发技术而构建的一个快速开发应用平台.用于帮助中小型软件企业建立一条适合市 ...
- .NET 动态脚本语言Script.NET 入门指南 Quick Start
Script.NET是一种动态的脚本语言,它使得程序可扩展,可定制,和维护性好.和Office系列的VB Script相似,可以在应用中嵌入大量的代码块,以便在运行时才执行这些代码. Script.N ...
- 使用expect脚本语言写一键发布服务(代码发布、所有服务重启)
互联网服务有很多台服务,但是在上线的时候需要将这些服务版本都更新与个个都重启,下面的脚本语言,就是一键发布服务~ 1.在/home/weihu/deploy/ 目录下建下publish .publis ...
- C#最良心脚本语言C#Light/Evil,Xamarin\WP8\Unity热更新最良心方案,再次进化.
C#Light的定位是嵌入式脚本语言,一段C#Light脚本是一个函数 C#Evil定位为书写项目的脚本语言,多脚本文件合作,可以完全用脚本承载项目. C#Light/Evil 使用完全C#一致性语法 ...
- [Java面试九]脚本语言知识总结.
核心内容概述 1.JavaScript加强,涉及到ECMAScript语法.BOM对象.DOM对象以及事件. 2.Ajax传统编程. 3.jQuery框架,九种选择器为核心学习内容 4.JQuery ...
随机推荐
- 浅析NopCommerce的多语言方案
前言 这段时间在研究多语言的实现,就找了NopCommerce这个开源项目来研究了一下,并把自己对这个项目的粗浅认识与大家分享一下. 挺碰巧的是昨天收到了NopCommerce 3.90 发布测试版的 ...
- samba服务搭建
文件传输 smb主配置文件 /etc/samba/smb.conf? yum install samba samba-client 要启动smb和nmb服务 修改安全级别 security = sha ...
- Linux ssh登录命令
常用格式:ssh [-l login_name] [-p port] [user@]hostname举例不指定用户:ssh 192.168.0.11指定用户:ssh -l root 192.168.0 ...
- Dapper的扩展这个你知道嘛?
之前写的ORM对比文章中,我选Dapper作为底层ADO的基础访问框架后,我对此再次进行进一步的深入研究,发现里面还有延伸了一些好用的扩展方法和特性,那我便简单的跟大家说一下特性标签. 一.Table ...
- ERP管理员培训报道
金秋十月,丹桂飘香,为期三天的“201610管理员培训”活动于2016年10月19日在苏州总部成功举行.参与本次培训活动的有浙江卡迪夫电缆有限公司.上海华源瓷业股份有限公司.江苏牛牌纺织机械有限公司. ...
- 对java数组的一些理解
刚开始学习Java的时候一直搞不清除获取数组的长度是用length()还是length,现在不妨来深入了解一下数组的真实面目. 我们不妨来看一下数组的源码,诶,数组的类名叫什么?我们声明一个int数组 ...
- Struts2框架(8)---Struts2的输入校验
Struts2的输入校验 在我们项目实际开发中在数据校验时,分为两种,一种是前端校验,一种是服务器校验: 客户端校验:主要是通过jsp写js脚本,它的优点很明显,就是输入错误的话提醒比较及时,能够减轻 ...
- ionic/cordvoa 修改platform文件夹里的文件,build会覆盖问题
这个问题开始让我感觉很崩溃,然后,我也很意外,如果遇到可以试一试 情景: 编辑platform/android/build.gradle 看了一下cordova 命令,好像还没这个命令,但是我还真的执 ...
- TypeScript设计模式之组合、享元
看看用TypeScript怎样实现常见的设计模式,顺便复习一下. 学模式最重要的不是记UML,而是知道什么模式可以解决什么样的问题,在做项目时碰到问题可以想到用哪个模式可以解决,UML忘了可以查,思想 ...
- Spring+SpringMVC+MyBatis+easyUI整合基础篇(十二)阶段总结
不知不觉,已经到了基础篇的收尾阶段了,看着前面的十几篇文章,真的有点不敢相信,自己竟然真的坚持了下来,虽然过程中也有过懒散和焦虑,不过结果还是自己所希望的,克服了很多的问题,将自己的作品展现出来,也发 ...