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

375 lines
7.3 KiB
Markdown
Raw Permalink 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.

# Control Flow
文档类型:语法主线
是否可直接用于生成代码:是
是否含已验证可执行示例:是
是否含已验证反例:是
遇到不确定时跳转到:[07_expressions_and_operators.md](07_expressions_and_operators.md)、[16_debug_and_profiler.md](16_debug_and_profiler.md)、[12_pitfalls.md](12_pitfalls.md)
手册位置:第 8 篇,共 32 篇。上一篇:[07_expressions_and_operators.md](07_expressions_and_operators.md)。下一篇:[09_objects_and_classes.md](09_objects_and_classes.md)。
这一篇只收录流程控制与异常控制,不讨论金融语义。
## 这一篇解决什么问题
回答“`if`、`case`、`for`、`while`、`repeat`、`break`、`continue`、`try`、`raise` 这些流程结构在 TSL 里到底怎么写,哪些写法已经被当前解释器实测验证过”。
## 必须记住的规则
- `if ... then ... else ...` 可以直接跟单条语句;需要多条语句时再补 `begin ... end`
- `for` 已验证支持 `to`、`downto`、可选 `step`,以及 `for i, v in array` 遍历。
- `while``repeat ... until` 都可直接使用;`repeat` 至少会先执行一轮再判断结束条件。
- `break` 会跳出当前最近一层循环,`continue` 会跳过当前轮剩余语句。
- `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`、计时和 profiler 这类“控制流补充工具”统一放到 [16_debug_and_profiler.md](16_debug_and_profiler.md)。
## 已验证语法
### `if`、`while`、`repeat ... until`
代码块身份:已验证可执行示例
```tsl
flag := 1;
if flag > 0 then
value := 1
else
value := 0;
counter := 0;
while counter < 3 do
counter := counter + 1;
repeat
counter := counter - 1;
until counter = 0;
```
### `for` 的几种主干写法
最基础的递增循环:
代码块身份:已验证可执行示例
```tsl
sum := 0;
for i := 0 to 2 do
sum := sum + i;
```
`step` 的递增循环:
代码块身份:已验证可执行示例
```tsl
s := 0;
for i := 1 to 5 step 2 do
s := s + i;
WriteLn(s);
```
已验证运行结果:
- 输出 `9`
`step``downto` 递减循环:
代码块身份:已验证可执行示例
```tsl
s := 0;
for i := 5 downto 1 step 2 do
s := s + i;
WriteLn(s);
```
已验证运行结果:
- 输出 `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` 开始
### `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`
`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`
### `case` 语句形态
普通分支:
代码块身份:已验证可执行示例
```tsl
a := 2;
case a of
1:
WriteLn("one");
2:
WriteLn("two");
else
WriteLn("other");
end;
```
已验证运行结果:
- 输出 `two`
并列标签与区间:
代码块身份:已验证可执行示例
```tsl
a := 4;
case a of
1, 2:
WriteLn("small");
3 to 5:
WriteLn("mid");
else
WriteLn("other");
end;
```
已验证运行结果:
- 输出 `mid`
### `case` 表达式形态
代码块身份:已验证可执行示例
```tsl
a := 2;
b := case a of
1:
"one";
2:
"two";
else
"other";
end;
WriteLn(b);
```
已验证运行结果:
- 输出 `two`
### `try ... except`
代码块身份:已验证可执行示例
```tsl
WriteLn("before");
try
raise "boom";
except
WriteLn("caught");
WriteLn(ExceptObject.ErrInfo);
end;
WriteLn("after");
```
已验证运行结果:
- 先输出 `before`
- 再输出 `caught`
- `ExceptObject.ErrInfo` 输出包含 `raise: boom` 的错误信息
- 最后继续输出 `after`
`ExceptObject` 的扩展字段:
代码块身份:已验证可执行示例
```tsl
program test;
begin
try
raise "boom";
except
WriteLn(ExceptObject.ErrLine);
WriteLn(ExceptObject.ErrNo);
end;
end.
```
已验证运行结果:
- `ExceptObject.ErrLine` 输出 `4`
- `ExceptObject.ErrNo` 输出 `2`
- 说明当前解释器里,异常对象除了 `ErrInfo` 以外,也能直接提供出错行号和错误号
### `try ... finally`
正常路径:
代码块身份:已验证可执行示例
```tsl
WriteLn("before");
try
WriteLn("body");
finally
WriteLn("finally");
end;
WriteLn("after");
```
已验证运行结果:
- 依次输出 `before`、`body`、`finally`、`after`
报错路径:
代码块身份:已验证可执行示例
```tsl
WriteLn("before");
try
WriteLn("body");
raise "boom";
finally
WriteLn("finally");
end;
WriteLn("after");
```
已验证运行结果:
- 先输出 `before`
- 再输出 `body`
- 然后仍会输出 `finally`
- 随后脚本报错终止,`after` 不会执行
### `raise`
代码块身份:已验证可执行示例
```tsl
WriteLn("before");
raise "boom";
WriteLn("after");
```
已验证运行结果:
- 先输出 `before`
- 随后脚本报错终止,`after` 不会执行
## 最小可编译示例
如果你只想先写一个最短条件分支,从这个开始:
代码块身份:已验证可执行示例
```tsl
flag := 1;
if flag > 0 then
value := 1
else
value := 0;
```
## 常见误写
-`else` 前面误加分号。
- 以为 `try ... finally` 会吞掉异常。
-`@case` 直接当成普通 `case` 表达式主写法。
- 在还没搞清表达式规则前,先把复杂业务函数塞进条件里。
- 把控制流问题和函数文件模型问题混在一起排查。
代码块身份:反例 / 不可照写
```text
if flag > 0 then
value := 1;
else
value := 0;
```
上面这种写法会编译失败,因为 `else` 前面的分号会让 `if` 语句在上一行提前结束。
代码块身份:反例 / 不可照写
```text
b := @case a of
1:
"one";
2:
"two";
else
"other";
end;
WriteLn(b);
```
我在 `2026-04-13` 用当前解释器实测,上面这类最小例子输出的是 `<STREXP>`,不能把它当成普通 `case` 表达式的可靠主语法写进新 session 默认生成结果。
## 跳转指引
- 回看条件表达式:见 [07_expressions_and_operators.md](07_expressions_and_operators.md)
- 继续封装成函数:见 [06_functions_and_calls.md](06_functions_and_calls.md)
-`goto`、`DEBUGRETURN`、计时和 profiler见 [16_debug_and_profiler.md](16_debug_and_profiler.md)