整数和浮点数

在Lua 5.3之前的版本中,只支持一种类型的数字,默认是浮点数,可以通过修改Lua解释器源码来使用整数。我理解这是因为Lua最初是被用作配置语言,面向的使用者大多不是程序员,是不区分整数和浮点数的,比如55.0就是两个完全一样的数字。后来随着Lua使用范围的扩大,同时支持整数的需求越发强烈(比如位运算),最终在Lua 5.3版本中区分了整数和浮点数。这也带来了一些复杂度,主要二元运算符对不同类型的处理规则,分为如下三类:

  • 支持整数和浮点数,包括+-*//%。如果两个操作数都是整数,则结果也是整数;否则(两个操作数至少有一个浮点数)结果是浮点数。
  • 只支持浮点数,包括/^。无论操作数是什么类型,结果都是浮点数。比如5/2,两个操作数虽然都是整数,但会转换为浮点数,然后计算结果为2.5
  • 只支持整数,包括5个位操作。要求操作数一定是整数,结果也是整数。

对上述三类的处理,在语法分析的常量折叠fold_const()函数和虚拟机执行时,都会体现。代码很繁琐,这里省略。

类型转换

Lua也定义了上述类型转换的规则(主要是不能完整转换情况下的规则):

  • 整型转浮点型:如果不能完整转换,则使用最接近的浮点数。即转换不会失败,只会丢失精度。
  • 浮点型转整型:如果不能完整转换,则抛出异常。

而Rust语言中,整型转浮点型规则一样,但浮点型转整型就不同了,没有检查是否能完整转换。这被认为是个bug并会修复。在修复前,我们只能自己做这个完整性的检查,即如果转换失败,则抛出异常。为此我们实现ftoi()函数:

pub fn ftoi(f: f64) -> Option<i64> {
    let i = f as i64;
    if i as f64 != f {
        None
    } else {
        Some(i)
    }
}

整型转浮点型时直接用as即可,而浮点型转整型时就需要用这个函数。

在语法分析和虚拟机执行阶段,都会涉及到这个转换,所以新建utils.rs文件用来放这些通用函数。

比较

Lua语言中,大部分情况下是尽量避免整数和浮点数的区别。最直接的例子就是,这个语句5 == 5.0的结果是true,所以Value::Integer(5)Value::Float(5.0),在Lua语言中是相等的。另外一个地方是,用这两个value做table的key的话,也认为是同一个key。为此,我们就要修改之前对Value的两个trait实现。

首先是比较相等的PartialEq trait:

impl PartialEq for Value {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Value::Integer(i), Value::Float(f)) |
            (Value::Float(f), Value::Integer(i)) => *i as f64 == *f && *i == *f as i64,

然后是Hash trait:

impl Hash for Value {
    fn hash<H: Hasher>(&self, state: &mut H) {
        match self {
            Value::Float(f) =>
                if let Some(i) = ftoi(*f) {
                    i.hash(state)
                } else {
                    unsafe {
                        mem::transmute::<f64, i64>(*f).hash(state)
                    }
                }

不过,还是有一个地方需要区分类型的,就是在语法分析时,向常量表中添加常量时,查询常量是否已经存在的时候。为此要实现一个区分类型的比较方法:

impl Value {
    pub fn same(&self, other: &Self) -> bool {
        // eliminate Integer and Float with same number value
        mem::discriminant(self) == mem::discriminant(other) && self == other
    }
}

测试

至此,二元运算语句的语法分析终于完成。虚拟机执行部分就很简单,这里略过。可以使用如下测试Lua代码:

g = 10
local a,b,c = 1.1, 2.0, 100

print(100+g) -- commutative, AddInt
print(a-1)
print(100/c) -- result is float
print(100>>b) -- 2.0 will be convert to int 2
print(100>>a) -- panic