playbook/docs/tsl/syntax/07_expressions_and_operator...

9.5 KiB
Raw Blame History

Expressions And Operators

文档类型:语法主线 是否可直接用于生成代码:是 是否含已验证可执行示例:是 是否含已验证反例:是 遇到不确定时跳转到:08_control_flow.md09_objects_and_classes.md12_pitfalls.md

手册位置:第 7 篇,共 32 篇。上一篇:06_functions_and_calls.md。下一篇:08_control_flow.md

这一篇集中放语言级表达式与运算符。agent 写代码时,只能从本页已验证代码块归纳表达式写法,不要把其他语言或外部资料里的运算符习惯直接搬进 TSL。

这一篇解决什么问题

回答“赋值、比较、条件求值、表达式对象、空安全访问和链式比较在 TSL 里怎样写”。

Agent 表达式/运算符判断流程

  1. 先判断要写赋值、比较、条件求值、表达式对象、空安全访问还是链式比较。
  2. 普通赋值只能用 :=,不要把 = 当赋值写法。
  3. 比较才用 =,并且把比较表达式放在 WriteLn(...)、条件或其他需要布尔值的位置。
  4. 已有变量做复合运算时,才使用 +=-=*=/=%=a++;a--;
  5. 条件求值优先用 flag ? true_value : false_value;需要 Pascal 风格时可用 if condition then true_value else false_value 的形态,但必须带 else
  6. 需要延迟求值或动态表达式对象时,才使用 @expr&"...",并用已验证的 eval(...) 形态求值。
  7. 空安全访问只照本页已验证形态写:a?.membera?.[index]、以及已验证的 c?.a?.[1]。不要外推成任意深度、任意组合都可写。
  8. 需要连续比较时,标量用 :< / :> 这组链式比较;数组逐元素比较用 ::< / ::> 这组矩阵链式比较。
  9. 没有已验证代码块时不要发明表达式/运算符写法。

必须记住的规则

  • 当前页只收已经单独验证过的基础表达式,不把未经逐条验证的扩展运算体系一次并进正文。
  • 普通赋值使用 :=
  • 已验证支持 +=-=*=/=%= 这几种基础运算赋值。
  • 已验证支持语句级 a++;a--;
  • = 用于比较,不用于赋值。
  • 已验证支持字符串 + 拼接、字符串比较和 like 正则匹配。
  • 已验证支持 flag ? true_value : false_valueif condition then true_value else false_value 这两种条件求值写法。
  • 已验证支持 @expr 把后面的内容声明成表达式对象。
  • 已验证支持 &"..." 把字符串编译成表达式对象。
  • 已验证支持逗号表达式 (exp1, exp2, ..., expN),并按从左到右顺序求值。
  • 已验证支持空安全访问 a?.membera?.[index] 和本页示例里的 c?.a?.[1]
  • 已验证支持 not innot likenot sqlinnot is 这几种否定形式运算。
  • 已验证支持标量链式比较 :>:<:<>:==:>=:<=
  • 已验证支持矩阵链式比较 ::>::<::<>::==::>=::<=
  • {$IFDEF ifexp} 可用于探测 if ... then ... else ... 表达式能力。
  • {$IFDEF nilinvoke} 可用于探测 nil 调用相关能力。

已验证语法

基础赋值和条件求值

代码块身份:已验证可执行示例

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

代码块身份:已验证输出片段

10

运算赋值:

代码块身份:已验证可执行示例

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

代码块身份:已验证输出片段

3

基础算术复合赋值:

代码块身份:已验证可执行示例

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

代码块身份:已验证输出片段

7
28
4
4

字符串同样支持 +=

代码块身份:已验证可执行示例

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

代码块身份:已验证输出片段

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

if 表达式:

代码块身份:已验证可执行示例

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

代码块身份:已验证输出片段

2

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

表达式对象

@ 表达式前导:

代码块身份:已验证可执行示例

A := 1;
B := @A + 1;
C := eval(B);
WriteLn(C);

代码块身份:已验证输出片段

2

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

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

代码块身份:已验证可执行示例

A := 1;
B := &"A + 1";
C := eval(B);
WriteLn(C);

代码块身份:已验证输出片段

2

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

逗号表达式:

代码块身份:已验证可执行示例

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

代码块身份:已验证输出片段

6

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

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

代码块身份:已验证可执行示例

A := (b := 2, c := 3, b * c) * c;
WriteLn(A);

代码块身份:已验证输出片段

18

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

空安全访问

代码块身份:已验证可执行示例

program test;
type Holder = class
    value;
end;
begin
    a := nil;
    WriteLn(a?.value = nil);
    h := new Holder();
    h.value := 7;
    WriteLn(h?.value);
    arr := nil;
    WriteLn(arr?.[0] = nil);
end.

代码块身份:已验证输出片段

1
7
1

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

代码块身份:已验证可执行示例

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

代码块身份:已验证输出片段

1

不要从这一段外推任意深度、任意成员/下标混合都可写。

否定形式运算

代码块身份:已验证可执行示例

program test;
type A = class
end;
type B = class
end;
begin
    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));
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

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

最小可编译示例

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

代码块身份:已验证可执行示例

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

代码块身份:已验证输出片段

10

常见误写

  • = 当赋值运算符。
  • if 表达式写成没有 else 的半句。
  • 把已验证的 c?.a?.[1] 外推成所有深链式空安全访问都可靠。

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

a = 1;

代码块身份:已验证输出片段

invalid statement

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

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

v := if 2 > 1 then 2;

代码块身份:已验证输出片段

invalid statement

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

跳转指引