Files
playbook/docs/tsl/syntax/06_expressions_and_operators.md
T
csh 540b3c0a1b 📝 docs(tsl): restructure agent-facing reference
Rework TSL syntax, catalog, modules, and routing docs around deterministic agent lookup and generation.

Split large catalog pages into focused function fact pages, remove obsolete pending/verified/unavailable paths, and update consistency tests for the new structure.
2026-06-09 17:24:30 +08:00

20 KiB
Raw Blame History

TSL 表达式与运算符

文档类型:语法主线 是否可直接用于生成代码:是 是否含可直接照写示例:是 是否含不可照写反例:是 遇到不确定时:先按本页表格和专题入口继续判断;03_values_and_literals.md04_variables_and_constants.md05_functions_and_calls.md07_control_flow.md08_objects_and_classes.md12_matrix_and_collections.md16_lexical_structure_and_compile_options.md11_pitfalls.md;仍不命中时回到语法路由中心 index.md;如果问题已经超出语法层,回到 TSL 总入口 ../index.md

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

本篇职责

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

智能体表达式/运算符判断流程

  1. 先判断要写基础表达式运算符,还是集合、矩阵、对象重载、运行时调用或 TS-SQL 这类专题运算符。
  2. 普通变量赋值只能用 :=,不要把 = 当赋值写法;const name = value; 是常量初始化规则,回看 04_variables_and_constants.md
  3. 在普通表达式里,比较才用 =,并且把比较表达式放在 writeLn(...)、条件或其他需要布尔值的位置。
  4. 函数签名里的默认参数 name = value 不是比较表达式;默认参数规则回 05_functions_and_calls.md
  5. 普通算术优先使用 +-*/%;整数除法和取模可用 divmod;幂运算用 ^;一元倒数用 !x
  6. 逻辑表达式优先使用 andornot&&|| 也已可用,但不要把 ! 写成逻辑非。
  7. 已有变量做原地更新时,才使用 +=-=*=/=%=^=.&=.|=.^=a++;a--;++a;--a;
  8. 条件求值优先用 flag ? true_value : false_value;需要 Pascal 风格时可用 if condition then true_value else false_value 的形态,但必须带 else
  9. 需要延迟求值或动态表达式对象时,才使用 @expr&"...",并用本页明确的 eval(...) 形态求值。
  10. 空安全访问只照本页文档明确形态写:a?.membera?.[index]、以及 c?.a?.[1]。不要外推成任意深度、任意组合都可写。
  11. 需要连续比较时,标量用 :< / :> 这组链式比较;数组逐元素比较用 ::< / ::> 这组矩阵链式比较。
  12. 命中集合、矩阵、对象重载、函数值调用、网格调用、运行时后缀或 TS-SQL 时,按“专题运算符入口表”跳到对应页面。
  13. {$ifdef ...} 只作为能力探测;编译选项边界回 16_lexical_structure_and_compile_options.md
  14. 任务需要的运算符没有文档事实时,不要生成猜测写法;只能切到对应专题页、项目自身文档或项目专属规则,仍无结论时记录文档缺口。

核心规则

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

本页直接生成的运算符

类别 运算符 / 形态 生成规则
普通赋值 := 变量赋值默认只用 :=
常量初始化 const name = value; 这是常量声明规则,不按普通赋值处理。
函数默认参数 name = value 这是函数签名规则,不按比较表达式处理。
复合赋值 +=-=*=/=%=^=.&=.&#124;=.^= 已有变量需要原地更新时使用。
自增 / 自减 a++a--++a--a 普通数字变量可直接用;对象重载语义回 24_object_overloads_and_iteration.md
算术 +-*/%divmod^ 普通数值计算使用;^ 是幂运算。
一元正负号 +x-x 可直接用于数值表达式。
一元倒数 !x 整型、实型输入返回实型倒数;矩阵逆/广义逆回 22_matrix_deep_dive.md
字符串连接 +$ 字符串拼接两种写法都已写入文档;默认优先用 +
比较 =<><><=>= = 只作比较,不作普通赋值。
逻辑 andornot&&、|| 默认优先写 and / or / not;不要用 ! 表示逻辑非。
位运算 .&.&#124;.^ 生成代码时写成无空格形态:.&.&#124;.^
成员 / 下标 / 调用 obj.membervalue[index]Func(args) 普通访问和调用可直接使用;类、对象、函数细节回对应专题。
空安全访问 a?.membera?.[index]c?.a?.[1] 只按已写入文档形态生成。
条件求值 flag ? true_value : false_value 普通条件表达式默认写法。
Pascal 风格条件表达式 if condition then true_value else false_value 必须带 else
表达式对象 @expr&"..." 需要延迟求值或动态表达式对象时使用,并用 eval(...) 求值。
逗号表达式 (exp1, exp2, ..., expN) 从左到右求值,返回最后一个表达式结果。
集合 / 匹配 / 类型关系 insqlinlikeis 否定形态见下一行;集合运算回 12_matrix_and_collections.md
否定关系 not innot sqlinnot likenot is 直接使用这几种文档明确形态,不自行重组。
标量链式比较 :<:>:<>:==:>=:<= 连续标量比较才使用。
矩阵链式比较 ::<::>::<>::==::>=::<= 数组逐元素链式比较才使用;数组与矩阵样数据细节回 12_matrix_and_collections.md

专题运算符入口

运算符 / 形态 责任页 生成规则
call(f, ...)##f(...)::FuncName(...) 05_functions_and_calls.md 函数值调用、变参转发和全局函数限定调用只按函数页生成。
调用点 in / out、变参 ... 05_functions_and_calls.md 这是参数传递 / 变参规则,不按普通表达式运算符处理。
#Func() with array(...) 10_runtime_context_and_with.md 运行时环境参数调用只按运行时上下文页生成。
#Func(args)timeout Ndupvalue(...) 10_runtime_context_and_with.md 网格调用和运行时服务后缀只按运行时上下文页生成。
union2intersectminusoutersect 12_matrix_and_collections.md 行集合并、交、差、对称差只按数组/集合页生成。
union、|、:&#124; 23_fmarray.md FMArray 行拼接和矩阵并右方(按列拼接)只按 FMArray 页生成。
->!matrix 22_matrix_deep_dive.md 数列数组初始化、矩阵逆/广义逆只按矩阵深水页生成。
反引号转置 `value 23_fmarray.md FMArray 转置只按 FMArray 页生成。
operator +operator <operator[]operator[0]operator[1]operator foroperator mrows/mcols/msizeoperator++operator += 24_object_overloads_and_iteration.md 对象运算符重载只按对象重载页生成。
select / sselect / vselect / mselectwheregroup byorder byjoin 14_ts_sql.md TS-SQL 是查询语法,不按普通表达式拼接。

默认生成规则:

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

按需生成规则:

  • 已有变量需要原地更新时,才用 +=-=*=/=%=^=.&=.|=.^=a++;a--;++a;--a;
  • 需要矩阵逆/广义逆时才使用 !A,并回 22_matrix_deep_dive.md 确认矩阵生成规则。
  • 位运算需要明确写成点前缀形态:.&.|.^;普通 & 不作为位与写法使用。
  • 需要延迟求值或动态表达式对象时,才用 @expr&"...",并用 eval(...) 求值。
  • 需要在一个表达式内按顺序执行多个子表达式时,才用逗号表达式 (exp1, exp2, ..., expN)
  • 空安全访问只按 a?.membera?.[index] 和本页示例里的 c?.a?.[1] 生成,不外推任意深链。
  • 否定形式只照 not innot likenot sqlinnot is 这几种文档明确写法生成。
  • 连续标量比较才用 :>:<:<>:==:>=:<=
  • 数组逐元素链式比较才用 ::>::<::<>::==::>=::<=

边界规则:

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

可直接照写示例

使用这些示例时遵守:

基础赋值和条件求值

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

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

代码块身份:输出片段

10

运算赋值:

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

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

代码块身份:输出片段

3

基础算术:

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

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

代码块身份:输出片段

3
3
12
4
1
2
1
8
2
3

基础比较:

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

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

代码块身份:输出片段

1
1
1
1
1
1

一元倒数:

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

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

代码块身份:输出片段

0.5
0.25
1
1

说明:

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

逻辑运算:

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

writeLn((1 < 2) and (2 < 3));
writeLn((1 > 2) or (2 < 3));
writeLn(not (1 > 2));
writeLn((1 < 2) && (2 < 3));
writeLn((1 > 2) || (2 < 3));

代码块身份:输出片段

1
1
1
1
1

位运算:

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

writeLn(6 .& 3);
writeLn(4 .| 1);
writeLn(6 .^ 3);

代码块身份:输出片段

2
5
5

基础算术复合赋值:

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

a := 10;
a -= 3;
writeLn(a);
a *= 4;
writeLn(a);
a /= 7;
writeLn(a);
a %= 5;
writeLn(a);

代码块身份:输出片段

7
28
4
4

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

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

a := 2;
a ^= 3;
writeLn(a);
b := 6;
b .&= 3;
writeLn(b);
c := 4;
c .|= 1;
writeLn(c);
d := 6;
d .^= 3;
writeLn(d);

代码块身份:输出片段

8
2
5
5

字符串同样支持 +=

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

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

代码块身份:输出片段

AB

$ 也可以用于字符串连接:

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

writeLn("A" $ "B");

代码块身份:输出片段

AB

字符串拼接、比较和 like

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

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

代码块身份:输出片段

222888
1
1
1
1

说明:

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

like 不要按 SQL % 通配去理解:

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

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

代码块身份:输出片段

1
0

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

自增与自减:

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

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

代码块身份:输出片段

2
1

前置自增与自减:

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

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

代码块身份:输出片段

2
1

if 表达式:

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

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

代码块身份:输出片段

2

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

表达式对象

@ 表达式前导:

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

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

代码块身份:输出片段

2

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

&"..." 表达式常量:

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

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

代码块身份:输出片段

2

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

逗号表达式:

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

writeLn(Demo());

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

代码块身份:输出片段

6

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

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

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

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

代码块身份:输出片段

18

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

空安全访问

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

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;

代码块身份:输出片段

1
7
1

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

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

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

代码块身份:输出片段

1

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

否定形式运算

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

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;

代码块身份:输出片段

1
1
1
1

标量链式比较

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

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

代码块身份:输出片段

1
1
1
1
1
1

矩阵链式比较

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

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]);

代码块身份:输出片段

1
0
1
1
0
0

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

条件编译探测

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

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

代码块身份:输出片段

1
1

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

默认生成模板

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

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

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

代码块身份:输出片段

10

禁止项

  • = 当赋值运算符。
  • == / != 当成等值 / 不等值比较;本页比较使用 = / <>
  • ! 当成逻辑非;本页逻辑非使用 not! 是一元倒数/矩阵逆相关运算符。
  • 把普通 & 当成位与;位与使用 .&
  • $= 当成字符串复合赋值;字符串原地拼接使用 +=
  • if 表达式写成没有 else 的半句。
  • 把本页明确的 c?.a?.[1] 外推成所有深链式空安全访问都可靠。
  • 从其他语言推断 TSL 运算符能力。

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

a = 1;

代码块身份:输出片段

invalid statement

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

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

v := if 2 > 1 then 2;

代码块身份:输出片段

invalid statement

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