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

485 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# TSL 控制流
文档类型:语法主线
是否可直接用于生成代码:是
是否含可直接照写示例:是
是否含不可照写反例:是
遇到不确定时:先按本页规则和示例继续判断;[05_functions_and_calls.md](05_functions_and_calls.md)、[06_expressions_and_operators.md](06_expressions_and_operators.md)、[15_debug_and_profiler.md](15_debug_and_profiler.md)、[11_pitfalls.md](11_pitfalls.md);仍不命中时回到语法路由中心 [index.md](index.md);如果问题已经超出语法层,回到 TSL 总入口 [../index.md](../index.md)
这一篇只收录流程控制与异常控制,不讨论金融语义。
## 本篇职责
回答“`if`、`case`、`for`、`while`、`repeat`、`break`、`continue`、`try`、`raise` 这些流程结构在 TSL 里到底怎么写,哪些写法可以直接生成”。
## 智能体控制流判断流程
1. 先判断任务需要条件分支、循环、`case`、异常处理还是调试跳转。
2. `if` / `for` / `while` / `repeat` 优先照本页文档骨架写,不要套用其他 Pascal 方言。
3. 生成带 `else` 的条件分支时,默认用 `begin ... end` 包住 `then``else` 分支,让分支内部语句正常以分号结尾;控制流块的 `end` 默认不加分号,也不要在 `else` 前提前加分号。
4. `case` 可写成语句形态,也可写成赋值右侧的表达式形态;表达式形态的分支只能放单条表达式/单条语句,不写 `begin ... end` 语句段。
5. 没有文档事实时不要发明控制流写法。
## 核心规则
- `if ... then ... else ...` 默认写成块式分支:`then begin ... end else begin ... end`。
- 块式分支内部的普通语句照常用分号结尾;语句形态的控制流块 `end` 默认不加分号。
- `for` 支持 `to`、`downto`、可选 `step`,以及 `for i, v in array` 遍历。
- `while``repeat ... until` 都可直接使用;`repeat` 至少会先执行一轮再判断结束条件。
- `break` 会跳出当前最近一层循环,`continue` 会跳过当前轮剩余语句。
- `case ... of ... else ... end` 可作为语句形态生成;语句形态的 `end` 后默认不加分号。
- `value := case ... of ... else ... end;` 可作为表达式形态生成;表达式形态赋值语句本身要用分号结尾。
- `case` 分支标签支持逗号并列和 `to` 区间。
- `try ... except ... end` 可以捕获 `raise` 产生的错误,并继续执行后续语句。
- `exceptObject.errInfo``except` 块中可读,能拿到当前错误信息。
- `exceptObject.errLine``exceptObject.errNo``except` 块中也可读。
- `try ... finally ... end` 无论是否报错,都会先执行 `finally`;如果没有 `except` 吞掉错误,脚本仍会在 `finally` 之后报错终止。
- `raise "message"` 是最小抛错写法。
- `goto`、`debugReturn`、`debugRunEnv`、计时和性能分析器这类“控制流补充工具”统一放到 [15_debug_and_profiler.md](15_debug_and_profiler.md)。
## 可直接照写示例
使用这些示例时遵守:
- 条件表达式、比较、布尔值和普通赋值回 [06_expressions_and_operators.md](06_expressions_and_operators.md)。
- 函数里的控制流只按控制流语法处理;函数文件模型、返回值和参数规则回 [05_functions_and_calls.md](05_functions_and_calls.md)。
- `goto`、`debugReturn`、计时和性能分析器不在本页生成,统一回 [15_debug_and_profiler.md](15_debug_and_profiler.md)。
### `if`、`while`、`repeat ... until`
代码块身份:可直接照写示例
```tsl
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);
```
代码块身份:输出片段
```text
1
0
```
### `for` 的几种主干写法
最基础的递增循环:
代码块身份:可直接照写示例
```tsl
sum := 0;
for i := 0 to 2 do
sum := sum + i;
writeLn(sum);
```
代码块身份:输出片段
```text
3
```
`step` 的递增循环:
代码块身份:可直接照写示例
```tsl
s := 0;
for i := 1 to 5 step 2 do
s := s + i;
writeLn(s);
```
输出说明:
- 输出 `9`
代码块身份:输出片段
```text
9
```
`step``downto` 递减循环:
代码块身份:可直接照写示例
```tsl
s := 0;
for i := 5 downto 1 step 2 do
s := s + i;
writeLn(s);
```
输出说明:
- 输出 `9`
代码块身份:输出片段
```text
9
```
数组遍历:
代码块身份:可直接照写示例
```tsl
data := array(10, 20, 30);
for i, v in data do
writeLn(i * 100 + v);
```
输出说明:
- 依次输出 `10`、`120`、`230`
- 这说明 `for i, v in data` 里的 `i``0` 开始
代码块身份:输出片段
```text
10
120
230
```
### `break` 与 `continue`
`break`
代码块身份:可直接照写示例
```tsl
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`
代码块身份:输出片段
```text
6
4
```
`continue`
代码块身份:可直接照写示例
```tsl
i := 0;
sum := 0;
while i < 4 do
begin
i := i + 1;
if i = 2 then
continue;
sum := sum + i;
end
writeLn(sum);
```
输出说明:
- 输出 `8`
代码块身份:输出片段
```text
8
```
### `case` 语句形态
普通分支:
代码块身份:可直接照写示例
```tsl
a := 2;
case a of
1:
writeLn("one");
2:
writeLn("two");
else
writeLn("other");
end
```
输出说明:
- 输出 `two`
代码块身份:输出片段
```text
two
```
并列标签与区间:
代码块身份:可直接照写示例
```tsl
a := 4;
case a of
1, 2:
writeLn("small");
3 to 5:
writeLn("mid");
else
writeLn("other");
end
```
输出说明:
- 输出 `mid`
代码块身份:输出片段
```text
mid
```
`case` 表达式形态:
代码块身份:可直接照写示例
```tsl
a := 3;
label_value := case a of
1, 2:
"small";
3, 4:
"mid";
else
"other";
end;
writeLn(label_value);
```
输出说明:
- 输出 `mid`
代码块身份:输出片段
```text
mid
```
说明:
- 表达式形态可以放在赋值右侧。
- 表达式形态的每个分支只写单条表达式/单条语句,不写 `begin ... end` 语句段。
- 赋值语句整体以 `end;` 收尾。
### `try ... except`
代码块身份:可直接照写示例
```tsl
writeLn("before");
try
raise "boom";
except
writeLn("caught");
writeLn(exceptObject.errInfo);
end
writeLn("after");
```
输出说明:
- 先输出 `before`
- 再输出 `caught`
- `exceptObject.errInfo` 输出包含 `raise: boom` 的错误信息
- 最后继续输出 `after`
代码块身份:输出片段
```text
before
caught
raise: boom
after
```
`exceptObject` 的扩展字段:
代码块身份:可直接照写示例
```tsl
try
raise "boom";
except
writeLn(exceptObject.errLine);
writeLn(exceptObject.errNo);
end
```
输出说明:
- `exceptObject.errLine` 输出 `4`
- `exceptObject.errNo` 输出 `2`
- 说明异常对象除了 `errInfo` 以外,也能直接提供出错行号和错误号
代码块身份:输出片段
```text
4
2
```
### `try ... finally`
正常路径:
代码块身份:可直接照写示例
```tsl
writeLn("before");
try
writeLn("body");
finally
writeLn("finally");
end
writeLn("after");
```
输出说明:
- 依次输出 `before`、`body`、`finally`、`after`
代码块身份:输出片段
```text
before
body
finally
after
```
报错路径:
代码块身份:可直接照写示例
```tsl
writeLn("before");
try
writeLn("body");
raise "boom";
finally
writeLn("finally");
end
writeLn("after");
```
输出说明:
- 先输出 `before`
- 再输出 `body`
- 然后仍会输出 `finally`
- 随后脚本报错终止,`after` 不会执行
代码块身份:输出片段
```text
before
body
finally
```
### `raise`
代码块身份:可直接照写示例
```tsl
writeLn("before");
raise "boom";
writeLn("after");
```
输出说明:
- 先输出 `before`
- 随后脚本报错终止,`after` 不会执行
代码块身份:输出片段
```text
before
```
## 默认生成模板
最短条件分支的默认骨架如下:
代码块身份:可直接照写示例
```tsl
flag := 1;
if flag > 0 then
begin
value := 1;
end
else
begin
value := 0;
end
```
## 禁止项
-`else` 前面误加分号。
- 生成没有分号的裸分支赋值,例如 `then value := 1 else ...`;带 `else` 时用块式分支。
- 以为 `try ... finally` 会吞掉异常。
-`case` 写成赋值右侧表达式。
- 在还没搞清表达式规则前,先把复杂业务函数塞进条件里。
- 把控制流问题和函数文件模型问题混在一起排查。
代码块身份:反例 / 不可照写
```text
if flag > 0 then
value := 1;
else
value := 0;
```
上面这种写法会编译失败,因为 `else` 前面的分号会让 `if` 语句在上一行提前结束。生成带 `else` 的赋值分支时,不要改成省略分号的裸分支;使用前文的块式分支。