# TSL 表达式与运算符

这一篇集中放语言级表达式与运算符。智能体写代码时，只使用本页和对应专题页已经明确的表达式事实，不要把其他语言或外部资料里的运算符习惯直接搬进 TSL。

## 本篇职责

<!-- section-id: syntax-06-001 -->

回答“赋值、算术、比较、逻辑、位运算、集合/类型关系、条件求值、表达式对象、访问/调用、点前缀运算符和专题运算符入口在 TSL 里怎样写”。

## 核心规则

<!-- section-id: syntax-06-002 -->

- 本页是 TSL 表达式与运算符的生成规则页；写代码时只使用本页或对应专题页明确记录的运算符和表达式形态。

### 本页直接生成的运算符

<!-- section-id: syntax-06-003 -->

<!-- tags: 运算符一览, 位运算, 逻辑运算, 算术运算, 比较运算, 移位, 按位与或 -->

<!-- prettier-ignore-start -->
| 类别 | 运算符 / 形态 | 生成规则 |
| --- | --- | --- |
| 普通赋值 | `:=` | 变量赋值默认只用 `:=`。 |
| 多变量赋值 | `[a, b] := array(...)`、`[a, ] := array(...)` | 需要从数组按位置拆出多个值时使用；只有一个接收变量时保留末尾逗号。 |
| 常量初始化 | `const name = value;` | 这是常量声明规则，不按普通赋值处理。 |
| 函数默认参数 | `name = value` | 这是函数签名规则，不按比较表达式处理。 |
| 算术复合赋值 | `+=`、`-=`、`*=`、`/=`、`\=`、`%=`、`^=`、`~=`、`div=` | 已有变量需要原地更新时使用；`div=` 是整除复合赋值。 |
| 点前缀复合赋值 | `.&=`、`.&#124;=`、`.^=`、`.&&=`、`.&#124;&#124;=` | 位运算或点前缀逻辑需要原地更新时使用。 |
| 自增 / 自减 | `a++`、`a--`、`++a`、`--a` | 普通数字变量可直接用；对象重载语义见 [23_object_overloads_and_iteration.md](23_object_overloads_and_iteration.md)。 |
| 算术 | `+`、`-`、`*`、`/`、`\`、`%`、`div`、`mod`、`^`、`~` | 普通数值计算使用；`\` 是左除，`^` 是幂运算，`~` 是对数运算。 |
| 一元正负号 | `+x`、`-x` | 可直接用于数值表达式。 |
| 一元倒数 | `!x` | 整型、实型输入返回实型倒数；矩阵逆/广义逆见 [21_matrix_deep_dive.md](21_matrix_deep_dive.md)。 |
| 字符串连接 | `+`、`$` | 字符串拼接两种写法都已写入文档；默认优先用 `+`。 |
| 比较 | `=`、`<>`、`<`、`>`、`<=`、`>=` | `=` 只作比较，不作普通赋值。 |
| 点前缀比较 | `.=`、`.<>`、`.<`、`.>`、`.<=`、`.>=` | 标量比较可用；数组/矩阵样数据上的逐元素语义见 [11_matrix_and_collections.md](11_matrix_and_collections.md)。 |
| 逻辑 | `and`、`or`、`not`、`&&`、`&#124;&#124;`、`.&&`、`.&#124;&#124;`、`.!!` | 默认优先写 `and` / `or` / `not`；不要用 `!` 表示逻辑非。 |
| 位运算 | `.&`、`.&#124;`、`.!`、`.^`、`shl`、`shr`、`rol`、`ror` | 生成代码时写成点前缀或关键字形态；普通 `&`、`&#124;`、`~` 不作为位运算写法。 |
| 成员 / 下标 / 调用 | `obj.member`、`value[index]`、`Func(args)` | 普通访问和调用可直接使用；类、对象、函数细节回对应专题。 |
| 空安全访问 | `a?.member`、`a?.[index]`、`c?.a?.[1]` | 只按已写入文档形态生成。 |
| 条件求值 | `flag ? true_value : false_value` | 普通条件表达式默认写法。 |
| 省略真值的条件求值 | `value ?: fallback_value` | 条件为真时返回条件自身的值；条件为假时返回后备值。 |
| Pascal 风格条件表达式 | `if condition then true_value else false_value` | 必须带 `else`。 |
| 表达式对象 | `@expr`、`&"..."` | 需要延迟求值或动态表达式对象时使用，并用 `eval(...)` 求值。 |
| 逗号表达式 | `(exp1, exp2, ..., expN)` | 从左到右求值，返回最后一个表达式结果。 |
| 集合 / 匹配 / 类型关系 | `in`、`sqlin`、`like`、`is` | 否定形态见下一行；集合运算见 [11_matrix_and_collections.md](11_matrix_and_collections.md)。 |
| 否定关系 | `not in`、`not sqlin`、`not like`、`not is` | 直接使用这几种文档明确形态，不自行重组。 |
| 标量链式比较 | `:<`、`:>`、`:<>`、`:==`、`:>=`、`:<=` | 连续标量比较才使用。 |
| 矩阵链式比较 | `::<`、`::>`、`::<>`、`::==`、`::>=`、`::<=` | 数组逐元素链式比较才使用；数组与矩阵样数据细节见 [11_matrix_and_collections.md](11_matrix_and_collections.md)。 |

### 专题运算符入口

<!-- section-id: syntax-06-004 -->

<!-- tags: 运算符去哪查, 特殊符号归属 -->

| 运算符 / 形态 | 责任页 | 生成规则 |
| --- | --- | --- |
| `call(f, ...)`、`##f(...)`、`::FuncName(...)` | [05_functions_and_calls.md](05_functions_and_calls.md) | 函数值调用、变参转发和全局函数限定调用只按函数页生成。 |
| 调用点 `in` / `out`、变参 `...` | [05_functions_and_calls.md](05_functions_and_calls.md) | 这是参数传递 / 变参规则，不按普通表达式运算符处理。 |
| `#Func() with array(...)` | [10_runtime_context_and_with.md](10_runtime_context_and_with.md) | 运行时环境参数调用只按运行时上下文页生成。 |
| `#Func(args)`、`timeout N`、`dupvalue(...)` | [10_runtime_context_and_with.md](10_runtime_context_and_with.md) | 网格调用和运行时服务后缀只按运行时上下文页生成。 |
| `union2`、`intersect`、`minus`、`outersect`、`union2=`、`intersect=`、`minus=`、`outersect=` | [11_matrix_and_collections.md](11_matrix_and_collections.md) | 行集合并、交、差、对称差及其复合赋值只按数组/集合页生成。 |
| `filterIn(...)`、`filterNotIn(...)` | [12_resultset_and_filters.md](12_resultset_and_filters.md) | 结果集过滤只按过滤页生成，不当作去重型集合运算。 |
| `:*`、`:/`、`:\`、`:^`、`union`、&#124;、`:&#124;`、`&#124;=`、`:&#124;=`、`&=`、`:*=`、`:/=`、`:\=`、`:^=` | [21_matrix_deep_dive.md](21_matrix_deep_dive.md)、[22_fmarray.md](22_fmarray.md) | 矩阵乘除、左右拼接、下方拼接和矩阵复合赋值只按矩阵专题页生成。 |
| `->`、`!matrix`、`.?`、`.?:` | [21_matrix_deep_dive.md](21_matrix_deep_dive.md) | 数列数组初始化、矩阵逆/广义逆、矩阵条件求值只按矩阵深水页生成。 |
| `::`、`::=`、`:.`、`:.=` | [21_matrix_deep_dive.md](21_matrix_deep_dive.md)、[23_object_overloads_and_iteration.md](23_object_overloads_and_iteration.md) | 矩阵遍历/深度遍历和对象遍历重载只按专题页生成，不在普通表达式里自行套用。 |
| 反引号转置 `` `value `` | [22_fmarray.md](22_fmarray.md) | `FMArray` 转置只按 `FMArray` 页生成。 |
| `operator +`、`operator <`、`operator[]`、`operator[0]`、`operator[1]`、`operator for`、`operator mrows/mcols/msize`、`operator++`、`operator +=` | [23_object_overloads_and_iteration.md](23_object_overloads_and_iteration.md) | 对象运算符重载只按对象重载页生成。 |
| `select` / `sselect` / `vselect` / `mselect`、`where`、`group by`、`order by`、`join` | [13_ts_sql.md](13_ts_sql.md) | TS-SQL 是查询语法，不按普通表达式拼接。 |
<!-- prettier-ignore-end -->

默认生成规则：

<!-- quickstart-rule: assignment -->

- 普通变量赋值使用 `:=`；`=` 在普通表达式里用于比较，不用于赋值。
- 常量初始化不按普通赋值判断，见 [04_variables_and_constants.md](04_variables_and_constants.md)。
- `const name = value;` 和函数签名默认参数 `name = value` 不按本页普通表达式比较判断。
- 普通算术使用 `+`、`-`、`*`、`/`、`\`、`%`、`div`、`mod`、`^`、`~`。
- 需要数值倒数时用 `!x`；整型和实型输入都会得到实型结果。
- 普通逻辑优先使用 `and`、`or`、`not`；`&&`、`||` 已写入文档但不作为默认主写法。
- 普通条件求值默认用 `flag ? true_value : false_value`。
- 需要把条件自身作为真值返回时，使用 `value ?: fallback_value`。
- 需要 Pascal 风格表达式时，才用 `if condition then true_value else false_value`，且必须带 `else`。
- 字符串拼接默认用 `+`；需要明确字符串连接时也可用 `$`。
- 字符串匹配用 `like` 时按正则理解，不按 SQL `%` 通配理解。

按需生成规则：

- 已有变量需要原地更新时，才用 `+=`、`-=`、`*=`、`/=`、`\=`、`%=`、`^=`、`~=`、`div=`、`.&=`、`.|=`、`.^=`、`.&&=`、`.||=`、`a++;`、`a--;`、`++a;`、`--a;`。
- 需要矩阵逆/广义逆时才使用 `!A`，矩阵生成规则以 [21_matrix_deep_dive.md](21_matrix_deep_dive.md) 为准。
- 位运算需要明确写成点前缀或关键字形态：`.&`、`.|`、`.!`、`.^`、`shl`、`shr`、`rol`、`ror`；普通 `&` 不作为位与写法使用。
- 点前缀比较 `.=`、`.<>`、`.<`、`.>`、`.<=`、`.>=` 命中数组/矩阵样数据时，逐元素语义以 [11_matrix_and_collections.md](11_matrix_and_collections.md) 为准。
- 需要延迟求值或动态表达式对象时，才用 `@expr` 或 `&"..."`，并用 `eval(...)` 求值。
- 需要在一个表达式内按顺序执行多个子表达式时，才用逗号表达式 `(exp1, exp2, ..., expN)`。
- 空安全访问只按 `a?.member`、`a?.[index]` 和本页示例里的 `c?.a?.[1]` 生成，不外推任意深链。
- 否定形式只照 `not in`、`not like`、`not sqlin`、`not is` 这几种文档明确写法生成。
- 连续标量比较才用 `:>`、`:<`、`:<>`、`:==`、`:>=`、`:<=`。
- 数组逐元素链式比较才用 `::>`、`::<`、`::<>`、`::==`、`::>=`、`::<=`。
- 混合两类以上运算符时，优先用括号明确分组，不依赖跨语言记忆里的优先级。
- TSL 的主要优先级从高到低是：成员/下标/调用，`not`、前置自增减与倒数/逆等一元运算，字符串 `$`，幂，乘除移位，加减（包括一元正负号）与集合/位运算，比较与 `in`/`like`/`is`，`and`/`or`，冒号，赋值，表达式前导 `@`；同级通常从左到右。

边界规则：

- 没有文档事实的运算符，不生成猜测写法，也不从其他语言习惯反推 TSL 语法。
- `{$ifdef ifexp}` 可用于探测 `if ... then ... else ...` 表达式能力；不要写成普通业务分支。
- `{$ifdef nilinvoke}` 可用于探测 nil 调用相关能力；编译选项细节见 [15_lexical_structure_and_compile_options.md](15_lexical_structure_and_compile_options.md)。

## 可直接照写示例

<!-- section-id: syntax-06-005 -->

使用这些示例时遵守：

- `:=` 是普通变量赋值写法；常量初始化规则见 [04_variables_and_constants.md](04_variables_and_constants.md)。
- 函数调用、命名参数、默认参数和函数值调用边界见 [05_functions_and_calls.md](05_functions_and_calls.md)；不要把函数签名里的 `name = value` 当成比较表达式。
- 分支/循环语句见 [07_control_flow.md](07_control_flow.md)；对象成员和类相关表达式见 [08_objects_and_classes.md](08_objects_and_classes.md)。
- 字符串字面量、拼接与文本边界见 [03_values_and_literals.md](03_values_and_literals.md)；数组扩展和矩阵样数据见 [11_matrix_and_collections.md](11_matrix_and_collections.md)。
- `{$ifdef ...}` 能力探测见 [15_lexical_structure_and_compile_options.md](15_lexical_structure_and_compile_options.md)，不要写成普通业务逻辑。

## 基础赋值和条件求值

<!-- section-id: syntax-06-006 -->

<!-- tags: 三目运算, 问号冒号, 条件表达式, 一行判断取值 -->

代码块身份：可直接照写示例

```tsl
a := 1;
b := 2;
flag := a < b;
value := flag ? 10 : 20;
writeLn(value);
```

代码块身份：输出片段

```text
10
```

省略真值的条件求值：

代码块身份：可直接照写示例

```tsl
writeLn(2 ?: 9);
writeLn(0 ?: 9);
```

代码块身份：输出片段

```text
2
9
```

多变量赋值：

代码块身份：可直接照写示例

```tsl
[a, b] := array(1, 2, 3);
writeLn(a);
writeLn(b);
[first_value, ] := array(4, 5);
writeLn(first_value);
```

代码块身份：输出片段

```text
1
2
4
```

运算赋值：

代码块身份：可直接照写示例

```tsl
a := 1;
a += 2;
writeLn(a);
```

代码块身份：输出片段

```text
3
```

## 基础算术与比较

<!-- section-id: syntax-06-017 -->

<!-- tags: 基础算术, 基础比较, 点前缀比较, 一元倒数, 加减乘除 -->

基础算术：

代码块身份：可直接照写示例

```tsl
writeLn(1 + 2);
writeLn(5 - 2);
writeLn(3 * 4);
writeLn(8 / 2);
writeLn(3 \ 2);
writeLn(9 % 4);
writeLn(9 div 4);
writeLn(9 mod 4);
writeLn(2 ^ 3);
writeLn(8 ~ 2);
writeLn(-3 + 5);
writeLn(+3);
```

代码块身份：输出片段

```text
3
3
12
4
0.666666666666667
1
2
1
8
3
2
3
```

基础比较：

代码块身份：可直接照写示例

```tsl
writeLn(1 < 2);
writeLn(2 > 1);
writeLn(2 <= 2);
writeLn(2 >= 2);
writeLn(2 = 2);
writeLn(2 <> 3);
```

代码块身份：输出片段

```text
1
1
1
1
1
1
```

点前缀比较：

代码块身份：可直接照写示例

```tsl
writeLn(2 .= 2);
writeLn(2 .<> 3);
writeLn(1 .< 2);
writeLn(2 .> 1);
writeLn(2 .<= 2);
writeLn(2 .>= 2);
```

代码块身份：输出片段

```text
1
1
1
1
1
1
```

一元倒数：

代码块身份：可直接照写示例

```tsl
a := 2;
b := 4.0;
ra := !a;
rb := !b;
writeLn(ra);
writeLn(rb);
writeLn(dataType(ra));
writeLn(dataType(rb));
```

代码块身份：输出片段

```text
0.5
0.25
1
1
```

说明：

- 整型 `a` 和实型 `b` 都可以用 `!` 求倒数。
- 上面两个 `dataType(...)` 都输出 `1`，表示结果是实型。

## 逻辑与位运算

<!-- section-id: syntax-06-018 -->

<!-- tags: and or not, 逻辑门, 按位运算, 移位运算 -->

逻辑运算：

代码块身份：可直接照写示例

```tsl
writeLn((1 < 2) and (2 < 3));
writeLn((1 > 2) or (2 < 3));
writeLn(not (1 > 2));
writeLn(.!! 0);
writeLn((1 < 2) && (2 < 3));
writeLn((1 > 2) || (2 < 3));
writeLn(1 .&& 0);
writeLn(0 .|| 2);
```

代码块身份：输出片段

```text
1
1
1
1
1
1
0
1
```

位运算：

代码块身份：可直接照写示例

```tsl
writeLn(6 .& 3);
writeLn(4 .| 1);
writeLn(6 .^ 3);
writeLn(.! 1);
writeLn(1 shl 2);
writeLn(8 shr 1);
writeLn(1 rol 1);
writeLn(2 ror 1);
```

代码块身份：输出片段

```text
2
5
5
-2
4
4
2
1
```

## 复合赋值与字符串运算

<!-- section-id: syntax-06-019 -->

<!-- tags: 复合赋值, 字符串运算, 字符串连接, like 正则, 原地更新 -->

基础算术复合赋值：

代码块身份：可直接照写示例

```tsl
a := 10;
a -= 3;
writeLn(a);
a *= 4;
writeLn(a);
a /= 7;
writeLn(a);
a %= 5;
writeLn(a);
b := 3;
b \= 2;
writeLn(b);
c := 8;
c ~= 2;
writeLn(c);
d := 7;
d div= 3;
writeLn(d);
```

代码块身份：输出片段

```text
7
28
4
4
0.666666666666667
3
2
```

幂运算和位运算也可以使用复合赋值：

代码块身份：可直接照写示例

```tsl
a := 2;
a ^= 3;
writeLn(a);
b := 6;
b .&= 3;
writeLn(b);
c := 4;
c .|= 1;
writeLn(c);
d := 6;
d .^= 3;
writeLn(d);
e := 0;
e .||= 2;
writeLn(e);
f := 1;
f .&&= 0;
writeLn(f);
```

代码块身份：输出片段

```text
8
2
5
5
1
0
```

字符串同样支持 `+=`：

代码块身份：可直接照写示例

```tsl
s := "A";
s += "B";
writeLn(s);
```

代码块身份：输出片段

```text
AB
```

`$` 也可以用于字符串连接：

代码块身份：可直接照写示例

```tsl
writeLn("A" $ "B");
```

代码块身份：输出片段

```text
AB
```

字符串拼接、比较和 `like`：

代码块身份：可直接照写示例

```tsl
writeLn("222" + "888");
writeLn("A" < "a");
writeLn("AB" < "ABC");
writeLn("ABC" = "ABC");
writeLn("2009-01-01" like "\\d{4}-\\d{2}-\\d{2}");
```

代码块身份：输出片段

```text
222888
1
1
1
1
```

说明：

- 字符串可以直接用 `+` 拼接。
- 字符串比较区分字符序和大小写；当前例子里 `"A" < "a"` 为真。
- `like` 的右侧可以直接写正则模式。

`like` 不要按 SQL `%` 通配去理解：

代码块身份：可直接照写示例

```tsl
writeLn("abc" like "a.*");
writeLn("abc" like "a%");
```

代码块身份：输出片段

```text
1
0
```

因此 `like` 更接近“正则匹配”，不是 SQL 那套 `%` / `_` 通配语义。

## 自增、自减与 if 表达式

<!-- section-id: syntax-06-020 -->

<!-- tags: 自增, 自减, 前置自增, 前置自减, if 表达式 -->

自增与自减：

代码块身份：可直接照写示例

```tsl
a := 1;
a++;
writeLn(a);
a--;
writeLn(a);
```

代码块身份：输出片段

```text
2
1
```

前置自增与自减：

代码块身份：可直接照写示例

```tsl
a := 1;
++a;
writeLn(a);
--a;
writeLn(a);
```

代码块身份：输出片段

```text
2
1
```

`if` 表达式：

代码块身份：可直接照写示例

```tsl
writeLn(if 2 > 1 then 2 else 1);
```

代码块身份：输出片段

```text
2
```

`if condition then true_value else false_value` 必须带 `else`，否则不是本页可照写的表达式形态。

## 运算符优先级

<!-- section-id: syntax-06-015 -->

<!-- tags: 运算符优先级, 谁先计算, 先乘除后加减, and or 优先级, 表达式加括号 -->

下表是面向当前正式文档已收录运算符的保守分组，数字越小优先级越高：

<!-- prettier-ignore-start -->
| 级别 | 主要形态 | 说明 |
| --- | --- | --- |
| 0 | `()`、`[]`、`.`、`?.`、函数调用 | 分组、访问、下标与调用最先结合。 |
| 1 | `not`、前置 `++` / `--`、`!`、`.!`、`.!!` | 逻辑非、前置自增减与倒数/逆等一元运算。 |
| 2 | `$` | 字符串连接。 |
| 3 | `^`、`~`、`:^` | 幂、开方与对应矩阵形态。 |
| 4 | `*`、`/`、`\`、`%`、`div`、`mod`、`shl`、`shr`、`rol`、`ror` | 乘除、取余与移位。 |
| 5 | `+`、`-`、一元正号 / 负号、集合运算、点前缀位/逻辑运算 | 加减、一元正负号及同组语言运算；因此幂先于一元正负号结合，例如 `-2 ^ 2` 等于 `-(2 ^ 2)`。 |
| 6 | 比较、`is`、`in`、`sqlin`、`like`、链式比较 | 关系判断。 |
| 7 | `and`、`or` | 低于比较，因此 `a > 1 and b < 2` 按两个比较再逻辑与理解。 |
| 8 | `:` | 冒号相关表达式形态。 |
| 9 | `:=` 与各类复合赋值 | 赋值接近最低优先级。 |
| 10 | `@` | 表达式对象前导最低。 |
<!-- prettier-ignore-end -->

同级运算通常从左到右求值。为了避免不同语言之间的优先级记忆混淆，混合两类以上运算符时仍推荐显式加括号：

代码块身份：可直接照写示例

```tsl
value := 1 + 2 * 3;
signedPower := -2 ^ 2;
flag := (2 > 1) and (1 < 2);
writeLn(value);
writeLn(signedPower);
writeLn(flag);
```

代码块身份：输出片段

```text
7
-4
1
```

稀有矩阵运算符的详细优先级以其专题页为准；不要用本表外推尚未写入正式文档的符号。

## 静态计算表达式 `static`

<!-- section-id: syntax-06-016 -->

<!-- tags: 静态计算, 只计算一次, 表达式缓存, static name, 按键缓存 -->

表达式前的 `static` 会缓存第一次计算结果。带 `name` 时，名称表达式是缓存键：同一键复用第一次结果，不同键分别计算。

代码块身份：可直接照写示例

```tsl
echo StaticValue(), ",", StaticValue();

function StaticValue();
begin
    return static NextValue();
end;

function NextValue();
begin
    global static_calls;
    static_calls += 1;
    return static_calls;
end;
```

代码块身份：输出片段

```text
1,1
```

按键分别缓存：

代码块身份：可直接照写示例

```tsl
echo NamedValue("A"), ",", NamedValue("A"), ",", NamedValue("B");

function NamedValue(key);
begin
    return static NextValue() name key;
end;

function NextValue();
begin
    global named_static_calls;
    named_static_calls += 1;
    return named_static_calls;
end;
```

代码块身份：输出片段

```text
1,1,2
```

这不是类成员的 `static` 字段；类静态成员见 [08_objects_and_classes.md](08_objects_and_classes.md)。缓存结果具有运行时状态，不要用它保存每次调用都必须重新计算的值。

## 表达式对象

<!-- section-id: syntax-06-007 -->

<!-- tags: 延迟求值, 表达式当值传, eval, 把式子存起来 -->

`@` 表达式前导：

代码块身份：可直接照写示例

```tsl
base_value := 1;
expr_value := @base_value + 1;
result_value := eval(expr_value);
writeLn(result_value);
```

代码块身份：输出片段

```text
2
```

`@base_value + 1` 会得到一个可交给 `eval(...)` 求值的表达式对象。

`&"..."` 表达式常量：

代码块身份：可直接照写示例

```tsl
base_value := 1;
expr_value := &"base_value + 1";
result_value := eval(expr_value);
writeLn(result_value);
```

代码块身份：输出片段

```text
2
```

`&"base_value + 1"` 会把字符串编译成表达式对象，再由 `eval(...)` 求值。

逗号表达式：

代码块身份：可直接照写示例

```tsl
writeLn(Demo());

function Demo();
begin
    return (a := 1, b := 2, c := 3, a + b + c);
end;
```

代码块身份：输出片段

```text
6
```

逗号表达式会按从左到右顺序执行前面的赋值，再返回最后一个表达式结果。

逗号表达式也可以继续参与外层计算：

代码块身份：可直接照写示例

```tsl
result_value := (b := 2, c := 3, b * c) * c;
writeLn(result_value);
```

代码块身份：输出片段

```text
18
```

逗号表达式本身可以作为一个普通子表达式继续参与后续运算。

## 空安全访问

<!-- section-id: syntax-06-008 -->

<!-- tags: 对象可能为空, 判空取属性, nil 安全, 避免空指针 -->

代码块身份：可直接照写示例

```tsl
a := nil;
writeLn(a?.value = nil);
h := new Holder();
h.value := 7;
writeLn(h?.value);
arr := nil;
writeLn(arr?.[0] = nil);

type Holder = class
    value;
end;
```

代码块身份：输出片段

```text
1
7
1
```

更深一层的混合空安全访问，本页只写入下面这个形态：

代码块身份：可直接照写示例

```tsl
c := nil;
writeLn(c?.a?.[1] = nil);
```

代码块身份：输出片段

```text
1
```

不要从这一段外推成所有深度、所有成员/下标组合都可写。

## 否定形式运算

<!-- section-id: syntax-06-009 -->

<!-- tags: 取反, 不包含, 不匹配, not in 怎么写 -->

代码块身份：可直接照写示例

```tsl
writeLn(1 not in array(2, 3));
writeLn("2009-1-1" not like "\\d{4}-\\d{2}-\\d{2}");
writeLn(1 not sqlin array(2, 3));
obj := new A();
writeLn(obj not is class(B));

type A = class
end;
type B = class
end;
```

代码块身份：输出片段

```text
1
1
1
1
```

## 标量链式比较

<!-- section-id: syntax-06-010 -->

<!-- tags: 连续比较, 区间判断, 夹在中间, 大小连写 -->

代码块身份：可直接照写示例

```tsl
writeLn(1 :< 2 :< 3);
writeLn(3 :> 2 :> 1);
writeLn(1 :== 1 :== 1);
writeLn(3 :>= 2 :>= 2);
writeLn(1 :<= 2 :<= 3);
writeLn(1 :<> 2 :<> 3);
```

代码块身份：输出片段

```text
1
1
1
1
1
1
```

## 矩阵链式比较

<!-- section-id: syntax-06-011 -->

<!-- tags: 数组逐元素比较, 整列比较, 批量比较 -->

代码块身份：可直接照写示例

```tsl
r := array(1, 2, -1) ::< array(2, 1, 0) ::< array(3, 2, 1);
writeLn(r[0]);
writeLn(r[1]);
writeLn(r[2]);
s := array(1, 2, -1) ::< 2 ::< array(3, 2, 1);
writeLn(s[0]);
writeLn(s[1]);
writeLn(s[2]);
```

代码块身份：输出片段

```text
1
0
1
1
0
0
```

矩阵链式比较会按元素位置分别得到结果数组，并且可以和标量混用。

## 条件编译探测

<!-- section-id: syntax-06-012 -->

<!-- tags: 判断支持哪些特性, 版本探测, ifdef 怎么用 -->

代码块身份：可直接照写示例

```tsl
{$ifdef ifexp}
writeLn(1);
{$else}
writeLn(0);
{$endif}
{$ifdef nilinvoke}
writeLn(1);
{$else}
writeLn(0);
{$endif}
```

代码块身份：输出片段

```text
1
1
```

这只能作为能力探测示例使用；智能体不要把条件编译探测写成普通业务逻辑。

## 默认生成模板

<!-- section-id: syntax-06-013 -->

如果你只需要最小的“比较 + 三目”例子，直接用这个：

代码块身份：可直接照写示例

```tsl
flag := 1 < 2;
value := flag ? 10 : 20;
writeLn(value);
```

代码块身份：输出片段

```text
10
```

## 禁止项

<!-- section-id: syntax-06-014 -->

- 用 `=` 当赋值运算符。
- 把 `==` / `!=` 当成等值 / 不等值比较；本页比较使用 `=` / `<>`。
- 把 `!` 当成逻辑非；本页逻辑非使用 `not`，`!` 是一元倒数/矩阵逆相关运算符。
- 把普通 `&` 当成位与；位与使用 `.&`。
- 把 `$=` 当成字符串复合赋值；字符串原地拼接使用 `+=`。
- 把 `if` 表达式写成没有 `else` 的半句。
- 把本页明确的 `c?.a?.[1]` 外推成所有深链式空安全访问都可靠。
- 从其他语言推断 TSL 运算符能力。
- 把类成员 `static field;` 与表达式前导 `static Expression [name Key]` 当成同一种语法。
- 在复杂混合表达式里依赖其他语言的优先级记忆而省略括号。

代码块身份：反例 / 不可照写

```text
a = 1;
```

代码块身份：输出片段

```text
invalid statement
```

上面这种写法会编译失败，因为单独的 `=` 在这里会被当成不成立的表达式。

代码块身份：反例 / 不可照写

```text
v := if 2 > 1 then 2;
```

代码块身份：输出片段

```text
invalid statement
```

上面这种写法也会编译失败；`if` 表达式必须带 `else`。
