三种应用场合
我们在调测阶段用Super脚本做开发,脚本写一点,调一点,每写一点脚本然后运行立即能看到运行效果,这是第一种应用场合。
功能调测通过后,将脚本形态的代码打包,生成即时编译代码,即时编译码被框架程序导入使用,提供完整产品功能,这是第二种应用场合。即时编译码可以有多种形式,包括原始字节码(*.bc文件)、加密字节码、本地机器码等,这几种形式视需要由用户自主选择。
还有一种场合是编写C/C++代码,为CSE-Super语言扩展lib库,扩展lib库的规则比较简单,自行研究几个样例就知道怎么做扩展了,lib库的对外接口要封装成Super语言支持的格式,包括调用格式要符合一定要求,接口是托管类型等。
我们说Super是一门简单的语言,指的是CSE-Super作为一门脚本语言,它很简洁,比JS简单,语言能力比JS 稍强。Super还是一门强大的语言,因为它有C/C++在背后支撑,现有C/C++库稍加封装即可纳入CSE-Super编程系统。这正如JS背后java语言,当JavaScript能力不足时,通常用Java开发插件透过API让JS调用。
即时编译(JIT)
解释执行与编译执行是相对而言的两种程序状态,我们说“相对”,一方面两者没有截然区分的界限,半编译半解释也是可能的,另一方面,两者均从特定观察角度而言的,即使是编译执行,对CPU来说,其本质也是解释执行的。或者,我们从根本上说,所有程序运行都是解释性的,所谓编译,实际是一种结合语义的代码优化过程。
代码优化可以深,也可以浅,比如下面代码:
i as SInt = 5; i++;
SInt从TCseObj继承而来,TCseObj定义了CSE统一的数据对象格式,SInt、SFloat、SStr、SBuff等都从TCseObj继承。SInt是将TCseObj按int方式使用的专项类封装,SFloat是对TCseObj按double方式使用的专项封装。上面代码即时编译时,我们既可以把i变量看作TCseObj类对象来编译,为区分起见,我们称之为“浅度即时编译”,编译效果相当于如下C++代码:
TCseObj i = TIntObj(5); CHECK_TRUE(Cse_type((CseObj)i) == dtInt); ((CseObj)i)->iValue += 1;
我们还可以把i变量看作int对象(即TIntObj类对象),而不是TCseObj来编译,为区分起见,我们称为“深度即时编译”,相当于如下C++代码:
TIntObj i = 5; i += 1;
TIntObj是这样定义的(下面只列构造函数与operator+=定义):
class TIntObj { private: int m_data; public: TIntObj(int i) { m_data = i; } inline void operator+=(int n) {m_data += n; } };
这种深度编译实际效果等同于:
int i = 5; i += 1;
采用深度即时编译,能让代码运行效率高到难以再去提升,但它有缺点,不能在调测阶段使用。因为所有CSE变量都是TCseObj对象,你随时可以选中脚本“i”执行来查看它的值的,在调测阶段只能采用前面提到的“浅度即时编译”,浅度编译运行效率偏低,因为TCseObj对TIntObj做了一层封装,增加了引用计次机制,另外还附外类型匹配检测(如前面举例的CHECK_TRUE语句)。
介绍到这儿,估计大家能猜出Super语言采用哪种即时编译了吧?没错,是深度编译,编译后代码效率接近于C++。这种编译限在代码调测稳定后用于“功能固化”,被编译代码不再依赖其它脚本定义,即:当前进行即时编译的代码的底层,不能再有用脚本写的东西了(除了回调函数)。
至于在调测阶段提供浅度即时编译,必要性不大,脚本跑得本来就慢,慢就慢吧。调测便利与代码高效运行难以兼得,所以暂不考虑浅度JIT。即时编译码可以与上层的CSE-SUPER脚本同时使用,所以,当脚本跑得慢影响调测时,你可以将部分耗时操作分离出来,按独立的包即时编译一下运行。
集成污染
什么是“集成污染”?简单地说,系统在加入某个模块之前一直好好的,加入这个摸块后就开始出现问题。集成污染有静态污染,也有动态污染,静态污染是编译、链接阶段引入的,污染可能来自宏定义、typedef定义、条件编译、C++模板等,动态污染则是程序运行才有的污染,比如,多线程模型混用、进程协调受干扰等。
Super语言为减少动态污染做了一些设计,主要是线程同步与事件驱动方面有专门考虑,本文不就此展开介绍,需要专门解释的是:Super语言如何减少(或消除)静态集成污染。
先看一个静态集成污染的例子:
class Math { static MyType max(MyType i1, MyType i2); };
你希望定义一个专有的max函数,这么去用“Math::max(arg1,arg2)”,编译会报错,因为系统头文件windef.h中已定义“#define max(a,b) (((a) > (b))?(a):(b))”。解除这个污染,只须加“#undef max”语句。
一般来说,静态集成污染具有难预见的特点,比如上面把max定义成宏很正常,因为max运算很常用,增加一个“Math::max”函数也很正常,你有为特定类型定义max运算,是在Math域名空间下定义。C/C++的静态集成污染,不可根本解决,只能缓解,控制宏开关与宏定义数量,尽量用class类或域名空间封装各种符号,少用或避免使用C++模板等,都可以缓解集成污染。
用C/C++为CSE_Super封装lib也只能“减缓”集成污染,但Super语言的即时编译是定位要“解决”静态集成污染的。这意味着:Super脚本编程系统中,不使用宏定义、宏开关,也没C++那种Template机制,注册到Super系统中的类型名称是确定的、透明可见的。另外,Super语言支持域名空间的表达形式,注册的类型如果不带域名空间标签,那它就是全局类型。
开发大规模程序
JavaScript不适合写大程序,除了性能瓶颈外,JS现有可用来模拟类继承的机制,也难以让它支持大规模软件开发。JS是原型导向语言,不是规范的面向对象语言,当不需要类继承时,类成员与类方法是可以分开定义的,类方法定义到prototype中,这时面向对象特性是够用的。
当需要类继承时,比方,我们定义了一个D2的类,用来表达二维空间特性,然后要定义一个Canvas画布,画布要继承D2特性。JavaScript对此通用做法是:创建一个空的object,然后初始化时把D2的属性及类方法拷贝到这个空object下,接着再把Canvas的属性与类方法也拷到该object下,Canvas的类方法要使用由D2定义的属性时,借助this变量直接存取,如“this.x”取的是D2下的属性,“this.bindImage”取的Canvas下的方法。这种机制的本质是:把两个类定义拷贝一起,借助this变量实现属性与类方法共享。
这样的类继承比较“便宜”,机制简单的同时,它比较原始,把两种或多种定义不管三七二十一捋一起,重名了就搞不清谁覆盖谁,编写小程序问题不大,但如果写大一些程序,数据经多次传递、组装后,很容易乱,至少让人感觉很乱。当然,JavaScript的机制很灵活,你可以构造一种不直接捋一起的类继承机制,但我相信你要付出不小代价,因为模拟子类取存(或调用)父类的属性(或方法)要有座桥,这座桥不好找。付出代价可能是易用性损失,也可能是性能损失,或两个都损失了。要不,“捋一起”做法怎么会成各种JS框架的一致选择呢?
Super语言也采用原型导向机制,同样没有规范的面向对象特性,它解决这个问题不是靠“捋一起”来实现类继承,而是通过内置SLinker链接机制,将模拟的“基类”与“派生类”连接起来,比如:
entity as S.e("D2,Canvas"); print(entity.Canvas.D2.x);
在Canvas下声明一个SLinker类型的成员D2,存取“entity.Canvas.D2.x”等效于“entity.D2.x”,这个D2是直接指向D2基类的。基类与派生类(如上面举例的D2与Canvas)并列成为新创建实体的成员,不像JS简单把它们的成员捋一起。
CSE-Super从设计伊始,就定位于支持大规模程序开发,要适应数十万行、乃至上百万行的代码规模,各项语言特性都顺从这个需求。除了上面介绍过的即时编译、域名空间、模拟类继承机制,其它细节设计还不少(如:引入包概念等),都为支持规模软件开发服务。