📝 docs(tsl): rebuild canonical syntax and routing manual

This commit is contained in:
csh
2026-04-22 16:59:20 +08:00
parent 3ed5052e61
commit 96b705b00b
83 changed files with 46446 additions and 323 deletions
+374
View File
@@ -0,0 +1,374 @@
# 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)