跳转至

运算、运算符

为什么不使用 operator 这样的关键字?

我认为 fn \add(MyInt rhs) -> MyInt 这样的写法与 auto operator+(MyInt rhs) -> MyInt 这样的写法相比更加简洁,也容易理解。

优先顺序:

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

前缀运算

运算 名称 解释 注释
+a pos 正号
-a neg 负号
~a flip 按位取反
!a not 逻辑取反
++a linc 前缀自增
--a ldec 前缀自减
a[] deref 解引用 你没办法重载指针类型
&a addr 取地址 不可重载

后缀运算

运算 名称 解释
a++ rinc 后缀自增
a-- rdec 后缀自减
a[] deref 解引用
a[b] index 数组索引

对于整数索引,应当是 isizeusize 类型。
对于键值索引,可以是任意类型。
注意数组索引必须写作 arr[1] 而不能是 1[arr]属于是远古遗物了

如果重载索引:

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

二元运算

算术运算符

运算 名称 解释 注释
a + b add 加法
a - b sub 减法
a * b mul 乘法
a / b div 除法
a % b mod 取模

位运算符

运算 名称 解释
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 类型,而不是像 C 中那样使用整数指针。

// 可以使用
f32 value = (123.456 as b32 << 1) as f32;
// 而不是
var f32 value = 123.456;
[u32] ptr = &value;
ptr[] <<= 1;

复合赋值运算符

运算 名称 解释
a += b iadd 加并赋值
a -= b isub 减并赋值
a *= b imul 乘并赋值
a /= b idiv 除并赋值
a %= b imod 取模并赋值
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;
        fn \eq(MyInt rhs) -> bool {
            return value == rhs.value;
        }
        fn \seq(MyInt rhs) -> bool {
            return value === rhs.value;
        }
        fn \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 && b and 逻辑与
a || b or 逻辑或
a ^^ b xor 逻辑异或

对于逻辑异或,请不要使用 expr1 != expr2 而是使用 expr1 ^^ expr2

其他运算符

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

注意这里和 C/C++ 不同,,. 不是运算符,而作为分隔符。
实际上你也不能够重载它们所以效果没有什么区别。

  • typeof 是唯一运算结果为类型的运算符,它可以直接当作类型使用。

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

  • 交换律:a + bb + a 等效
  • 结合律:(a + b) + ca + (b + c) 等效

算术运算符简写

我们约定以下运算符等效,其互换不应影响程序的行为:

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

对于自定义运算符也应当遵循此约定。

自定义运算符无法定义 xx= 形式的复合赋值运算符,而是由其定义 xx 二元运算符后自动生成对应的复合赋值运算符。

a = a + ba += b
a = a - ba -= b
a = a * ba *= b
a = a / ba /= b
依此类推

自增、自减

a++a += 1
a--a -= 1

a if expr else b 是唯一的条件表达式。
a and ba && b
a or ba || b
not a!a

逻辑运算符规则

&& || ^^ 符合交换律,即:

a && bb && a 等效
a || bb || a 等效
依此类推

重载运算符

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

运算 名称 运算 名称 运算 名称
a + b add a & b band a && b and
a - b sub a \| b bor a \|\| b or
a * b mul a ^ b bxor a ^^ b xor
a / b div a << b shl a == b eq
a % b mod 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++ rinc a-- rdec a <=> b cmp
++a linc --a ldec a[] deref

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

\ 开头的运算符

有些运算符并没有对应的符号版本,但也非常重要,这些运算符以 \ 开头。

为什么不使用特定名称的函数?

我认为不应该出现语法或优化和一些非标准库的特定名称函数有关的情况,所以我将它们定义为运算符。
不过实际上还是有一些特定名称的函数会被编译器识别,例如 add sub 等。

  • \hash 计算哈希值
  • \next 获取下一个值
  • \prev 获取上一个值
  • \clone 克隆对象
  • \sizeof 获取对象大小
  • \alignof 获取对象对齐值

优化假设

运算律

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

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

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

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

比较传递性

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

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

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

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

对应运算符 peq pord 等,为浮点数实现这些并将 == <=> 等运算符绑定到它们上面。

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

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

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

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

附加变更(目前未合并到文档)

我希望将余数与模运算区分开来:

  • rem 取余运算,符号与被除数相同
  • mod 取模运算,符号与除数相同

返回的而类型与被除数相同。

表达式 rem (取余) mod (取模)
5, 3 2 2
-5, 3 -2 1
5, -3 2 -1
-5, -3 -2 -2

对于浮点数,我们定义如下运算:

  • frem 浮点数取余运算
  • fmod 浮点数取模运算

对于需要对齐的情况,我们定义如下运算:

  • alignup 向上对齐运算
  • aligndown 向下对齐运算
  • alignnear 向最近对齐运算
  • isaligned 判断对齐运算
  • ceil 向上取整运算,仅对浮点数有效,在编译时已知整数类型上会导致编译错误
  • floor 向下取整运算,仅对浮点数有效,在编译时已知整数类型上会导致编译错误

ceilfloor 也在整数类型上定义,返回其原值,但会导致编译错误以防止误用。


数学运算扩展:

  • addition
    • + add 普通加法(溢出时抛出异常)
    • +^ addo 饱和加法(溢出时返回类型最大值或最小值)
    • +% addw 带宽加法(溢出时截断为类型位宽)
    • +! addc 带进位加法(返回结果与进位标志)
  • subtraction
    • - sub 普通减法(溢出时抛出异常)
    • -^ subo 饱和减法(溢出时返回类型最大值或最小值)
    • -% subw 带宽减法(溢出时截断为类型位宽)
    • -! subc 带借位减法(返回结果与借位标志)
  • multiplication
    • * mul 普通乘法(溢出时抛出异常)
    • *^ mulo 饱和乘法(溢出时返回类型最大值或最小值)
    • *% mulw 带宽乘法(溢出时截断为类型位宽)
    • *! mulc 带溢出乘法(返回结果与溢出值)
  • division
    • / div 普通除法
    • /% divrem 带余数除法(返回商与余数)
    • /+ divceil 向上取整除法
    • /- divfloor 向下取整除法

对于可能抛出异常的操作都在编译时进行检查,如果能证明不会抛出异常则不会生成运行时检查代码,但如果用户进行了错误的假设则可能导致大范围的未定义行为。

这点目前写在 暂存草案 中。

对于浮点数类型:

  • + fadd 浮点数加法
  • - fsub 浮点数减法
  • * fmul 浮点数乘法
  • / fdiv 浮点数除法
  • % frem 浮点数取余

符号表示只是暂定的,但名称已经确定。