# 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` 用当前解释器实测,上面这类最小例子输出的是 ``,不能把它当成普通 `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)