常用运算符

运算符是 Flurry 语言中用于执行计算、比较、逻辑组合等操作的特殊符号。Flurry 提供了一组旨在清晰且富有表现力的运算符集合。

算术运算符

用于执行基本的数学运算。这些运算符通常可以被用户定义的类型通过实现特定的 Trait 来重载。

  • 加法 (+): a + b
  • 减法 (-): a - b
  • 乘法 (*): a * b
  • 除法 (/): a / b (整数除法通常向零截断,浮点数除法遵循 IEEE 754)
  • 取模 (%): a % b (计算除法的余数)

空格敏感性: 如词法结构中所述,这些二元算术运算符必须在其两侧使用空格,以便被词法分析器正确识别为标准算术运算符 token(例如 _+_)。a+b 不会被识别为标准的加法运算。

let sum = count + 1;
let difference = total - tax;
let area = width * height;
let quotient = dividend / divisor;
let remainder = value % modulus;

比较运算符

用于比较两个值,结果通常是一个布尔值 (bool)。它们也可以被重载。

  • 等于 (==): a == b
  • 不等于 (!=): a != b
  • 小于 (<): a < b
  • 小于等于 (<=): a <= b
  • 大于 (>): a > b
  • 大于等于 (>=): a >= b

空格敏感性: 小于 (<) 和大于 (>) 运算符同样具有空格敏感性。当用于比较时,它们两侧必须有空格(对应 _<__>_ token)。这避免了与泛型参数列表等场景中使用的尖括号 (<, >) 产生词法歧义。

let is_equal = response_code == 200;
let is_not_empty = size != 0;
let in_range = value >= min_value and value <= max_value; -- 注意 'and' 关键字
let needs_update = current_version < latest_version;

逻辑运算符

用于组合布尔值。Flurry 使用关键字而非符号来表示逻辑与和或。

  • 逻辑与 (and): a and b (短路求值:如果 afalse,则不评估 b)
  • 逻辑或 (or): a or b (短路求值:如果 atrue,则不评估 b)
  • 逻辑非 (not): not a (一元运算符)
let both_conditions_met = is_ready and has_permission;
let either_option_valid = is_primary or is_fallback;
let is_invalid = not is_valid;

赋值运算符

用于将值赋给变量绑定。

  • 简单赋值 (=): variable = value
  • 复合赋值:
    • +=, -=, *=, /=, %= (例如 a += b 等价于 a = a + b)
let score = 0;
score += 10; -- score 现在是 10

注意:由于 Flurry 可能默认绑定是可变的,let 声明的 score 可以被复合赋值操作修改。

其他重要操作符/结构

Flurry 还包含一些具有特殊语法和用途的操作符或结构,其中许多采用后缀形式(详见下一节):

  • 选择运算符 (.): 用于访问成员、调用方法、限定路径等。
  • 解引用 (.*): 后缀操作符,用于访问指针指向的值。
  • 取引用 (.ref): 后缀操作符,用于获取一个值的指针。
  • 取像 ('): 后缀操作符,用于 expr ' image 操作。
  • 调用操作符: () (函数调用), [] (索引调用), {} (expr object 调用/记录调用), <> (泛型/钻石调用)。
  • 错误/可选处理: ! (错误传播/处理), ? (可选类型处理)。
  • 效应处理: # (效应处理块)。
  • 属性标记 (^): 用于附加属性。
  • 管道操作符 (|, |>): 用于函数式风格的数据流处理。

编译时连接运算符 (++)

  • ++: 这个运算符专用于编译时,用于连接两个集合类的值。
  • 适用类型: 其具体行为取决于操作数类型所实现的特定 Trait(可重载)。常见的应用包括:
    • 连接编译时字符串 (str)。
    • 连接编译时列表 (meta.List)。
    • 连接编译时集合 (meta.Set)。
    • 连接 bitvec 抽象视图(编译器会将其优化为位操作)。
  • 限制: 不能直接用于运行时的值(除了 bitvec 抽象视图映射这种特殊情况,其本质也是编译时指导的优化)。
comptime {
    const part1 = "Hello, ";
    const part2 = "Flurry!";
    const message = part1 ++ part2; -- message == "Hello, Flurry!"

    const list1 = meta.List.new(1, 2);
    const list2 = meta.List.new(3, 4);
    const combined_list = list1 ++ list2; -- combined_list == [1, 2, 3, 4]

    -- let runtime_vec1 = Vec.new();
    -- let runtime_vec2 = Vec.new();
    -- let result = runtime_vec1 ++ runtime_vec2; -- 编译错误!++ 不能用于运行时 Vec
}

关于位运算符

值得再次强调,Flurry 不提供内建的按位运算符(如 C/Java/Rust 中的 &, |, ^, ~, <<, >>)。进行位级别的操作需要:

  1. 使用 bitvec 抽象视图: 通过取像操作 value'bitvec 获取值的位向量表示,然后对其进行连接 (++) 或其他位操作(这些操作会被编译器优化)。
  2. 使用标准库函数: Flurry 计划提供一个内置的 math(或其他)包,其中包含执行位移、按位与/或/异或等操作的函数(例如 math.bit_and(a, b))。

这种设计选择旨在减少操作符集合的歧义,并将底层位操作封装在更明确的抽象(bitvec)或库函数中。

运算符优先级与结合性

Flurry 使用 Pratt 解析器常用的绑定力 (Binding Power) 或等效的优先级方案来确定复杂表达式中运算符的计算顺序。您提供的 OpTable Scala 代码片段清晰地展示了不同 token 对应的优先级数值(数值越高,通常绑定得越紧密)。

例如,根据该表:

  • 乘法/除法/取模/++ (70) 的优先级高于加法/减法 (60)。
  • 加法/减法 (60) 的优先级高于比较运算符 (40)。
  • 比较运算符 (40) 的优先级高于逻辑与 (30) 和逻辑或 (20)。
  • 后缀操作符如选择 (., 100)、取像 (', 100)、调用 ((), [], {}, <>, 90) 和可能的字面量拓展 (id, 110) 具有非常高的优先级。

开发者通常不需要死记硬背完整的优先级表,而是遵循数学和逻辑运算的常规顺序,并在必要时使用圆括号 () 来明确指定求值顺序。

let result = a + b * c; -- 等价于 a + (b * c),因为 * 优先级更高
let flag = x > 0 and y < 10; -- 等价于 (x > 0) and (y < 10)
let complex = (a + b) * (c - d); -- 使用括号强制优先级