跳转至

运算、运算符

Lumos 使用 \ 加运算名的方式定义运算符重载,比 operator+ 的写法更简洁。

优先顺序:

  1. 后缀
  2. 前缀
  3. 二元
    1. 乘、除、取余
    2. 加、减
    3. 移位
    4. 按位与
    5. 按位异或
    6. 按位或
    7. 逻辑与 (and)
    8. 逻辑异或 (xor)
    9. 逻辑或 (or)
    10. 比较
    11. 赋值

前缀运算

运算 名称 解释 注释
+a pos 正号
-a neg 负号
~a flip 按位取反
!a not 逻辑取反
&a addr 取地址 不可重载

后缀运算

运算 名称 解释
a[] deref 解引用
a[b] index 数组索引

对于整数索引,应当是 isizeusize 类型。
对于键值索引,可以是任意类型。
对于数组索引,推荐的重载方式:

def \index(usize i) -> i32 {
    return (array + i)[];
}

二元运算

算术运算符

运算 名称 解释 注释
a + b add 加法
a - b sub 减法
a * b mul 乘法
a / b div 除法
a % b rem 取余 符号与被除数相同
mod 取模 符号与除数相同

取余与取模

Lumos 区分取余(rem)与取模(mod)两种运算,它们在负数情况下行为不同:

表达式 rem mod
5, 3 2 2
-5, 3 -2 1
5, -3 2 -1
-5, -3 -2 -2
  • rem:结果符号与被除数相同,等同于 C/C++ 的 %
  • mod:结果符号与除数相同,等同于数学取模。

对于浮点数,对应的命名为 frem(浮点取余)和 fmod(浮点取模)。

除法扩展

运算 名称 解释 注释
a / b div 普通除法
a /% b divrem 带余数除法 返回商与余数元组
a /+ b divceil 向上取整除法
a /- b divfloor 向下取整除法

算术运算扩展

对于整数运算,Lumos 提供溢出行为可控的扩展运算符:

运算 名称 解释
a +^ b addo 饱和加法(溢出时返回类型最大值或最小值)
a +% b addw 环绕加法(溢出时截断为类型位宽)
a +! b addc 进位加法(返回 (结果, 进位) 元组)
a -^ b subo 饱和减法
a -% b subw 环绕减法
a -! b subc 借位减法(返回 (结果, 借位) 元组)
a *^ b mulo 饱和乘法
a *% b mulw 环绕乘法
a *! b mulc 宽乘法(返回 (结果, 高位) 元组)

普通运算符(+, -, *)在整数溢出时抛出异常;编译器会尽可能在编译期检查,若能证明不溢出则不生成运行时检查代码。相关优化假设详见 控制流

浮点数运算

浮点数使用与整数相同的运算符(+-*/%),但其内部重载名称不同:

运算 浮点名称 解释
a + b fadd 浮点加法
a - b fsub 浮点减法
a * b fmul 浮点乘法
a / b fdiv 浮点除法
a % b frem 浮点取余

浮点数模运算(符号与除数相同)使用 fmod,通过 .mod(b) 调用。
浮点数不支持位运算,请将浮点数转换为 bits 类型后再操作(见位运算符节)。

对齐运算

对于需要内存对齐的场景,Lumos 提供以下命名运算:

名称 解释
alignup 向上对齐(将值调整为不小于给定对齐量的最小倍数)
aligndown 向下对齐(将值调整为不大于给定对齐量的最大倍数)
alignnear 向最近对齐(四舍五入到最近的对齐倍数)
isaligned 判断值是否已对齐到给定对齐量

对于取整运算:

  • ceil:向上取整,仅对浮点数有效。在编译期已知为整数类型时会导致编译错误。
  • floor:向下取整,仅对浮点数有效。在编译期已知为整数类型时会导致编译错误。

ceilfloor 在整数类型上也有定义(返回其原值),但会触发编译错误以防止误用。

位运算符

运算 名称 解释
a & b band 按位与
a | b bor 按位或
a ^ b bxor 按位异或
a << b shl 左移
a >> b shr 右移
a <<< b ushl 无符号左移
a >>> b ushr 无符号右移
a <<> b rol 循环左移
a >>< b ror 循环右移

注意浮点数不能进行位运算,请将浮点数转换为 bits 类型:

f32 value = (123.456 as b32 << 1) as f32;

复合赋值运算符

运算 名称 解释
a += b iadd 加并赋值
a -= b isub 减并赋值
a *= b imul 乘并赋值
a /= b idiv 除并赋值
a %= b irem 取余并赋值
a &= b iband 按位与并赋值
a |= b ibor 按位或并赋值
a ^= b ibxor 按位异或并赋值
a <<= b ishl 左移并赋值
a >>= b ishr 右移并赋值

比较运算符

运算 名称 解释 注释
a == b eq 等于
a != b ne 不等于
a < b lt 小于
a <= b le 小于等于
a > b gt 大于
a >= b ge 大于等于
a <=> b cmp 比较
a === b seq 严格相等 可重载
a !== b sne 严格不等 可重载
a $== b beq 二进制相等 不可重载
a ~== b bne 二进制不等 不可重载
a &== b same 相同 不可重载
a &!= b diff 不同 不可重载
  • <=>
    比较运算符,返回 -1、0、1 表示小于、等于、大于。

    对于重载,实现 <=> 即可,==!=<<=>>= 会自动调用 <=>

    当然你也可以手动实现其它函数,Lumos 会自动将未实现的比较运算符转换为已实现的。

  • === !==
    严格相等,仅当两个对象的类型和值都相同时返回 true。

    你没听错,严格相等是可重载的,但你不能重载不同类型的严格相等运算。
    对于严格相等运算,你可以使用与 == 不同的逻辑。

    struct MyInt {
        i32 value;
        def \eq(MyInt rhs) -> bool {
            return value == rhs.value;
        }
        def \seq(MyInt rhs) -> bool {
            return value === rhs.value;
        }
        def \seq(f64 rhs) -> bool; // 这是不行的,不同类型间的严格相等不可重载
    }
    
  • $== ~==
    二进制相等,仅当两个对象的二进制表示完全相同时返回 true。
    注意二进制表示的比较包含位数,例如 i32 1 $== i64 1 为 false。
  • &== &!=
    判断左右是否是相同的对象,不比较值。
    类似于 &a == &b,任意一边为字面量时始终得到 false。
    &==&!= 是唯一可以比较引用的运算符。

    i32 a = 1;
    ref i32 b = a;
    i32 c = a;
    assert(a &== b); // true
    assert(a &== c); // false
    

逻辑运算符

运算 名称 解释 注释
a and b and 逻辑与(有短路)
a or b or 逻辑或(有短路)
a xor b xor 逻辑异或

and/or 支持短路求值:a and b 中若 a 为假则不对 b 求值;a or b 中若 a 为真则不对 b 求值。前缀运算符 not a 等价于 !a

对于逻辑异或,应使用 a xor b 而非 a != b

bool 类型还可以使用位运算符 &/| 进行无短路的逻辑与/或运算:

运算 解释
a & b 逻辑与(无短路,仅限 bool
a | b 逻辑或(无短路,仅限 bool

其他运算符

运算 名称 解释 注释
a as type cast 类型转换 不可重载
sizeof(a) sizeof 计算大小 不可重载
typeof(a) typeof 获取类型 不可重载
typenameof(a) typenameof 获取类型名称 不可重载
a->b arrow 自定义成员访问 可重载

注意 ,. 不是运算符,而作为分隔符。

  • typeof 运算结果为类型,可以直接当作类型使用。

    i32 a = 1;
    typeof(a) b = 2;
    
  • typenameof 的运算结果为字符串。

可空传播 ?

后缀运算符 ? 用于标记一个可空表达式。当 ? 标记的值为空(nullnone)时,整个含 ? 的前方完整表达式跳转到 : 后的后备值。

my_func(a?) : b       // 若 a 为空则返回 b,否则返回 my_func(a)
my_func(a?, c?) : b   // 若 a 或 c 任意一个为空则返回 b,否则返回 my_func(a, c)
my_func(a?) + 1 : b   // : b 修饰前方整个表达式;若 a 为空则返回 b,否则返回 my_func(a) + 1
(my_func(a?) : b) + 1 // 使用括号显式控制范围

? 同时进行类型窄化:传递给函数时参数类型从 T? 窄化为 T,无需显式解包。

等价展开示例:

my_func(a?, c?) : b
// 等价于
if (a == null or c == null) b else my_func(a, c)

运算律

复合赋值运算符应与基本运算符等效:

a = a 运算符 b;  ⟺  a 运算符= b;

对应规则:

  • a = a + ba += b
  • a = a - ba -= b
  • a = a * ba *= b
  • a = a / ba /= b

自定义运算符会自动生成对应的复合赋值运算符。

Lumos 不支持 ++-- 运算符,请改用 a += 1a -= 1

逻辑运算符交换律

符合交换律:

a and bb and a
a or bb or a
a xor bb xor a

运算符重载

与 C++ 中 operator+ 一类的写法不同,Lumos 使用反斜杠加运算名来代表运算符,例如 \add

运算 名称 运算 名称 运算 名称
a + b add a & b band a and b and
a - b sub a \| b bor a or b or
a * b mul a ^ b bxor a xor b xor
a / b div a << b shl a == b eq
a % b rem a >> b shr a != b ne
-a neg ~a flip !a not
a get a < b lt a <= b le
a = b set a > b gt a >= b ge
a <=> b cmp
a[] deref

所有类似 a += b 的运算名称都是 i 加上对应的运算符名称,例如 add 的增量运算符是 iadd

\ 开头的运算符

一些没有对应符号的重要运算符:

  • \hash 计算哈希值
  • \next 获取下一个值
  • \prev 获取上一个值
  • \clone 克隆对象
  • \sizeof 获取对象大小
  • \alignof 获取对象对齐值
  • \get 获取值(读取操作,配合 \set 用于属性语义)
  • \set 设置值(写入操作)
  • \as 类型转换(返回类型可重载,代表显式转换语义)

优化假设

运算律

对于运算律,我们将它们实现为特性:

  • Commutative 交换律
  • Associative 结合律
  • Distributive 分配律

实现了这些特性的运算符可以被编译器用来进行优化。

默认情况下我们要求 add mul 实现交换律与结合律,mul 实现分配律。

比较传递性

对于比较运算符,我们将传递性实现为特性:

  • TransitiveEq 等于传递性
  • TransitiveCmp 比较传递性
  • SelfConsistent 自反性

对于浮点数,我们还实现了可能无法比较的特性:

  • PartialEq 部分等于性
  • PartialOrd 部分有序性

对应运算符为 peqpord 等。我们为浮点数提供这些实现,并将 ==<=> 等运算符与之绑定。

浮点默认遵循 IEEE 754 标准的比较规则,但也同时实现全序浮点类型 totalf32 totalf64

实现了这些特性的比较运算符可以被编译器用来进行优化。

一般情况下我们要求 eq 实现等于传递性与自反性,cmp 实现比较传递性与自反性。

在比较上实现自反性需要对于对象 a b,如果 a > b 为 true 则 b < a 必须为 true,反之亦然。


相关内容:表达式语法