作者:dlite@163.com
最近在设计一种语法类似C#的脚本语言,我们称之为N语言,简称nlang。关于编译和初始化静态常量有一些注意事项,记录在本文中。为描述方便,下面将静态常量简称为常量。
最近在设计一种语法类似C#的脚本语言,我们称之为N语言,简称nlang。关于编译和初始化静态常量有一些注意事项,记录在本文中。为描述方便,下面将静态常量简称为常量。
先考虑nlang中支持的几种静态常量定义:
static const float BASE_FACT = 3.1415927; //1
static const float SCALE_IN_FACT = 2.53 * BASE_FACT; //2
static const uint MAX_CONN = getConfigConn(); //3
static const Bar MY_BAR = {1, "a bar"}; //4
对于表达式1,处理很简单,编译时直接替换为字面常量就可以。
对于表达式2,似乎可以直接在编译时定值,实际未竟如此。如果语言支持动态连接,假设表达式1位于模块A中,且表达式2位于模块B中。那么表达式1的改变并独立编译模块A后,如果不重新编译模块B,我们无法保证表达式2能正确更新。这种情况,常量表达式的值在链接时才能确定。
对于表达式3,依赖于运行时配置,显然需要运行时定值。
表达式4是一个对象常量,为初始化相关RTTI信息,也需要运行时定值。
因此要支持上述功能,我们需要考虑以下问题:
如何处理依赖于其他非字面常量的常量定义?
如何处理依赖于函数的常量?
如何处理对象常量?
在解决这些问题的同时,还需要保持语言的简洁性,避免规则过于复杂;性能因素也不能忽视。
下面给出最终采用的处理规则和方法。
首先,我们规定编译时的可定值变换:
1)恒同变换;
2)基本类型操作符(+-*/等)对应的内置函数。[1]
其次,我们规定编译时的可定值性:
1)基本类型字面常量,可以在编译时直接定值。
例如:
static const int A = 100;
static const string KITTY_NAME = "Tommy";
2)作用于字面常量的可定值变换,可以在编译时直接定值。
例如:
static const int X = 1234 + 5678; //整数加法运算是可定值变换
3)作用于同一个编译单元中已定值常量的可定值变换,可以在编译时直接定值。
例如:
static const int X = 1;
static const int Y = X + 1; //Y可编译时定值,因为X在同一编译单元中已定值。
然后. 对于简单类型的其他非字面常量,比如依赖于函数的常量。分两种情况处理:
1)如果所依赖的内容可在编译时定值,则在编译时计算并定值。
2)如果所依赖的内容不能在编译时定值,则在模块加载时统一定值。[2]
上述依赖定值需要递归进行,并且在发现循环依赖时,提示编译错误。
最后,对于对象常量,因为需要设置RTTI信息,在模块加载时统一定值。
备注:
[1] 以后做进一步优化时,可以参考C语言,规定更宽泛一些。例如,某些内置函数也支持可定值变换,例如cos()等,或者通过支持Annotation来通用化。
[2] 模块加载时定值,发生在动态链接之后,用户初始化模块函数之前。在此处理之前,所有未定值静态量都会被初始化为其缺省值。