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

8.8 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

这一篇集中放语言级表达式与运算符,避免与业务计算示例混用。

这一篇解决什么问题

回答“赋值、比较、条件求值、可空访问和一部分新表达式在 TSL 里怎样写”。

必须记住的规则

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

已验证语法

基础赋值和条件求值

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

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

运算赋值:

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

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

已验证运行结果:

  • 输出 3

基础算术复合赋值:

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

program test;
begin
    a := 10;
    a -= 3;
    WriteLn(a);
    a *= 4;
    WriteLn(a);
    a /= 7;
    WriteLn(a);
    a %= 5;
    WriteLn(a);
end.

已验证运行结果:

  • a -= 3 后输出 7
  • a *= 4 后输出 28
  • a /= 7 后输出 4
  • a %= 5 后输出 4

字符串同样支持 +=

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

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

已验证运行结果:

  • 输出 AB

字符串拼接、比较和 like

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

program test;
begin
    WriteLn("222" + "888");
    WriteLn("A" < "a");
    WriteLn("AB" < "ABC");
    WriteLn("ABC" = "ABC");
    WriteLn("2009-01-01" like "\\d{4}-\\d{2}-\\d{2}");
end.

已验证运行结果:

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

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

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

program test;
begin
    WriteLn("abc" like "a.*");
    WriteLn("abc" like "a%");
end.

已验证运行结果:

  • 第一行输出 1
  • 第二行输出 0
  • 因此当前解释器里的 like 更接近“正则匹配”,不是 SQL 那套 % / _ 通配语义

自增与自减:

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

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

已验证运行结果:

  • 先输出 2
  • 再输出 1

if 表达式:

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

program test;
begin
    WriteLn(if 2 > 1 then 2 else 1);
end.

已验证运行结果:

  • if 2 > 1 then 2 else 1 返回 2

@ 表达式前导:

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

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.

已验证运行结果:

  • a?.value = nil 输出 1
  • h?.value 输出 7
  • arr?.[0] = 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 not in array(2, 3) 输出 1
  • "2009-1-1" not like "\\d{4}-\\d{2}-\\d{2}" 输出 1
  • 1 not sqlin array(2, 3) 输出 1
  • obj not is class(B) 输出 1

标量链式比较

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

program test;
begin
    WriteLn(1 :< 2 :< 3);
    WriteLn(3 :> 2 :> 1);
    WriteLn(1 :== 1 :== 1);
    WriteLn(3 :>= 2 :>= 2);
    WriteLn(1 :<= 2 :<= 3);
    WriteLn(1 :<> 2 :<> 3);
end.

已验证运行结果:

  • 1 :< 2 :< 3 输出 1
  • 3 :> 2 :> 1 输出 1
  • 1 :== 1 :== 1 输出 1
  • 3 :>= 2 :>= 2 输出 1
  • 1 :<= 2 :<= 3 输出 1
  • 1 :<> 2 :<> 3 输出 1

矩阵链式比较

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

program test;
begin
    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]);
end.

已验证运行结果:

  • array(1, 2, -1) ::< array(2, 1, 0) ::< array(3, 2, 1) 的三个元素依次输出 101
  • array(1, 2, -1) ::< 2 ::< array(3, 2, 1) 的三个元素依次输出 100
  • 说明矩阵链式比较会按元素位置分别得到结果数组,并且可以和标量混用

条件编译探测

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

program test;
begin
{$IFDEF ifexp}
    WriteLn(1);
{$ELSE}
    WriteLn(0);
{$ENDIF}
{$IFDEF nilinvoke}
    WriteLn(1);
{$ELSE}
    WriteLn(0);
{$ENDIF}
end.

已验证运行结果:

  • {$IFDEF ifexp} 输出 1
  • {$IFDEF nilinvoke} 输出 1

最小可编译示例

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

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

flag := 1 < 2;
value := flag ? 10 : 20;

常见误写

  • = 当赋值运算符。
  • if 表达式写成没有 else 的半句。
  • 把更深链式可空访问 c?.a?.[1] 直接当成当前解释器已支持事实。

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

a = 1;

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

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

v := if 2 > 1 then 2;

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

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

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

上面这种更深的混合可空访问,在当前已记录验证里没有通过,不要提前把后续版本的修正结果写进结论。

跳转指引