playbook/docs/tsl/syntax/07_control_flow.md

9.0 KiB
Raw Blame History

TSL 控制流

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

这一篇只收录流程控制与异常控制,不讨论金融语义。

本篇职责

回答“ifcaseforwhilerepeatbreakcontinuetryraise 这些流程结构在 TSL 里到底怎么写,哪些写法可以直接生成”。

智能体控制流判断流程

  1. 先判断任务需要条件分支、循环、case、异常处理还是调试跳转。
  2. if / for / while / repeat 优先照本页文档骨架写,不要套用其他 Pascal 方言。
  3. 生成带 else 的条件分支时,默认用 begin ... end 包住 thenelse 分支,让分支内部语句正常以分号结尾;不要在 else 前提前加分号。
  4. case 可写成语句形态,也可写成赋值右侧的表达式形态;表达式形态的分支只能放单条表达式/单条语句,不写 begin ... end 语句段。
  5. 没有文档事实时不要发明控制流写法。

核心规则

  • if ... then ... else ... 默认写成块式分支:then begin ... end else begin ... end
  • 块式分支内部的普通语句必须用分号结尾。
  • 控制流块的 begin ... end 后可以加分号也可以不加(语法都允许)。
  • for 支持 todownto、可选 step,以及 for i, v in array 遍历。
  • whilerepeat ... until 都可直接使用;repeat 至少会先执行一轮再判断结束条件。
  • break 会跳出当前最近一层循环,continue 会跳过当前轮剩余语句。
  • case ... of ... else ... end 可作为语句形态生成;end 后可以加分号也可以不加。
  • value := case ... of ... else ... end; 可作为表达式形态生成;表达式形态赋值语句本身要用分号结尾。
  • case 分支标签支持逗号并列和 to 区间。
  • try ... except ... end 可以捕获 raise 产生的错误,并继续执行后续语句。
  • exceptObject.errInfoexcept 块中可读,能拿到当前错误信息。
  • exceptObject.errLineexceptObject.errNoexcept 块中也可读。
  • try ... finally ... end 无论是否报错,都会先执行 finally;如果没有 except 吞掉错误,脚本仍会在 finally 之后报错终止。
  • raise "message" 是最小抛错写法。
  • gotodebugReturndebugRunEnv、计时和性能分析器这类“控制流补充工具”统一放到 15_debug_and_profiler.md

可直接照写示例

使用这些示例时遵守:

ifwhilerepeat ... until

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

flag := 1;
if flag > 0 then
begin
    value := 1;
end
else
begin
    value := 0;
end

counter := 0;
while counter < 3 do
    counter := counter + 1;

repeat
    counter := counter - 1;
until counter = 0;
writeLn(value);
writeLn(counter);

代码块身份:输出片段

1
0

for 的几种主干写法

最基础的递增循环:

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

sum := 0;
for i := 0 to 2 do
    sum := sum + i;
writeLn(sum);

代码块身份:输出片段

3

step 的递增循环:

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

s := 0;
for i := 1 to 5 step 2 do
    s := s + i;
writeLn(s);

输出说明:

  • 输出 9

代码块身份:输出片段

9

stepdownto 递减循环:

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

s := 0;
for i := 5 downto 1 step 2 do
    s := s + i;
writeLn(s);

输出说明:

  • 输出 9

代码块身份:输出片段

9

数组遍历:

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

data := array(10, 20, 30);
for i, v in data do
    writeLn(i * 100 + v);

输出说明:

  • 依次输出 10120230
  • 这说明 for i, v in data 里的 i0 开始

代码块身份:输出片段

10
120
230

breakcontinue

break

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

i := 0;
sum := 0;
while true do
begin
    i := i + 1;
    if i > 3 then
        break;
    sum := sum + i;
end
writeLn(sum);
writeLn(i);

输出说明:

  • sum 输出 6
  • i 输出 4

代码块身份:输出片段

6
4

continue

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

i := 0;
sum := 0;
while i < 4 do
begin
    i := i + 1;
    if i = 2 then
        continue;
    sum := sum + i;
end
writeLn(sum);

输出说明:

  • 输出 8

代码块身份:输出片段

8

case 语句形态

普通分支:

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

a := 2;
case a of
1:
    writeLn("one");
2:
    writeLn("two");
else
    writeLn("other");
end

输出说明:

  • 输出 two

代码块身份:输出片段

two

并列标签与区间:

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

a := 4;
case a of
1, 2:
    writeLn("small");
3 to 5:
    writeLn("mid");
else
    writeLn("other");
end

输出说明:

  • 输出 mid

代码块身份:输出片段

mid

case 表达式形态:

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

a := 3;
label_value := case a of
1, 2:
    "small";
3, 4:
    "mid";
else
    "other";
end;
writeLn(label_value);

输出说明:

  • 输出 mid

代码块身份:输出片段

mid

说明:

  • 表达式形态可以放在赋值右侧。
  • 表达式形态的每个分支只写单条表达式/单条语句,不写 begin ... end 语句段。
  • 赋值语句整体以 end; 收尾。

try ... except

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

writeLn("before");
try
    raise "boom";
except
    writeLn("caught");
    writeLn(exceptObject.errInfo);
end
writeLn("after");

输出说明:

  • 先输出 before
  • 再输出 caught
  • exceptObject.errInfo 输出包含 raise: boom 的错误信息
  • 最后继续输出 after

代码块身份:输出片段

before
caught
raise: boom
after

exceptObject 的扩展字段:

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

try
    raise "boom";
except
    writeLn(exceptObject.errLine);
    writeLn(exceptObject.errNo);
end

输出说明:

  • exceptObject.errLine 输出 4
  • exceptObject.errNo 输出 2
  • 说明异常对象除了 errInfo 以外,也能直接提供出错行号和错误号

代码块身份:输出片段

4
2

try ... finally

正常路径:

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

writeLn("before");
try
    writeLn("body");
finally
    writeLn("finally");
end
writeLn("after");

输出说明:

  • 依次输出 beforebodyfinallyafter

代码块身份:输出片段

before
body
finally
after

报错路径:

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

writeLn("before");
try
    writeLn("body");
    raise "boom";
finally
    writeLn("finally");
end
writeLn("after");

输出说明:

  • 先输出 before
  • 再输出 body
  • 然后仍会输出 finally
  • 随后脚本报错终止,after 不会执行

代码块身份:输出片段

before
body
finally

raise

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

writeLn("before");
raise "boom";
writeLn("after");

输出说明:

  • 先输出 before
  • 随后脚本报错终止,after 不会执行

代码块身份:输出片段

before

默认生成模板

最短条件分支的默认骨架如下:

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

flag := 1;
if flag > 0 then
begin
    value := 1;
end
else
begin
    value := 0;
end

禁止项

  • else 前面误加分号。
  • 生成没有分号的裸分支赋值,例如 then value := 1 else ...;带 else 时用块式分支。
  • 以为 try ... finally 会吞掉异常。
  • case 写成赋值右侧表达式。
  • 在还没搞清表达式规则前,先把复杂业务函数塞进条件里。
  • 把控制流问题和函数文件模型问题混在一起排查。

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

if flag > 0 then
    value := 1;
else
    value := 0;

上面这种写法会编译失败,因为 else 前面的分号会让 if 语句在上一行提前结束。生成带 else 的赋值分支时,不要改成省略分号的裸分支;使用前文的块式分支。