25d895d 🐛 fix(gitea_workflow): clean up temp repos after job steps 2bc3b11 🐛 fix(gitea_workflow): clean up temporary repo dirs in workflows 98c3f30 📝 docs(agent_rules): allow plan execution on current branch 16c7230 📝 docs(prompts): define custom verify layering 8efc4dd 🐛 fix(skills): quote commit-message description bc8498f 🐛 fix(ci): install tomli for gitea tests 55cda3b 🐛 fix(tests): report missing toml parser clearly c0729c7 🐛 fix(playbook): import Optional for cli compatibility 63e24bf 📦 deps(skills): sync thirdparty skills d2f9356 🐛 fix(ci): isolate gitea workflow repos 588b81d 🐛 fix(ci): inline gitea workflow bootstrap e0b1c3a ♻️ refactor(skills): standardize first-party skill contracts 2c5050d ♻️ refactor(skills): rename repo skills source dir f049dfb 📦 deps(skills): drop duplicate first-party superpowers skills 234b335 ✨ feat(workflow): add superpowers planning and execution state tracking c1702a6 📝 docs(markdown): format tracked markdown and drop stale templates 2325409 📝 docs(markdown): clarify optional markdownlint usage 214c44e 🔧 chore(markdown): add markdownlint baseline and lint fixes a22b324 📝 docs(templates): add execution and memory-bank prompt templates 223a797 📝 docs(templates): update README for Claude Code and current features 4ac8672 📝 docs: simplify README + platform-agnostic tools + auto-create local rules 2431c9d 📝 docs: add claude_md config and use cross-platform paths d64b248 📝 docs: fix README.md inaccuracies and add Claude Code info c8d6bf2 🐛 fix(playbook): use relative paths in CLAUDE.md when not at project root 6518f0f ✨ feat(playbook): auto-create CLAUDE.md with path discovery 6ec9a45 ✨ feat(skills): add skill_link symlink support + platform-agnostic prompt 9f8b6b5 📝 docs: update README and config example for Claude Code support 79cff6c 📝 docs(skills): add Claude Code platform support 452c6f5 ✨ feat(playbook): auto-inject AGENTS.md into CLAUDE.md e1dbf3c 🐛 fix(skills): remove dual-path from commit-message skill f3a7259 🔧 chore(ci): use prepare_repo.sh in both workflows da08212 🔧 chore(ci): extract prepare_repo.sh and clean up workflows 7ade85e 🗑️ remove(tsl): drop syntax_book/, data/ source and build script f94dba0 ♻️ refactor(skills): update playbook.py and tests for thirdparty/ layout b3df412 ♻️ refactor(skills): separate thirdparty skills into thirdparty/ subdirectory 64950e7 📦 deps(skills): sync thirdparty skills a2e3cb0 ✨ feat(playbook): add no_backup deploy controls 8609d59 🐛 fix(docs): repair reference catalog source links 956da11 🐛 fix(playbook): publish hidden ci test fixes 3f67754 📦 deps(skills): sync thirdparty skills 08ca87b 📦 deps(skills): add karpathy thirdparty sync 96b705b 📝 docs(tsl): rebuild canonical syntax and routing manual 3ed5052 📦 deps(skills): sync thirdparty skills 60108dd 📦 deps(skills): sync thirdparty skills da85d4e 🐛 fix(thirdparty): prune nested project snapshots a2a697e 📦 deps(skills): sync thirdparty skills 9df610a 🐛 fix(thirdparty): exclude duplicated superpowers skills 33dd5bb 🐛 fix(thirdparty): preserve optional manifest fields 91b0ea7 🐛 fix(thirdparty): preserve manifest during snapshot update 2e26f98 🔧 chore(thirdparty): generalize skills sync pipeline 5b9c1e3 📦 deps(skills): sync superpowers 2f2d34a 📝 docs(readme): normalize subtree command spacing 62db7db 🐛 fix(ci): serialize superpowers update and sync 3463223 🐛 fix(ci): use literal superpowers sync paths 48f6de8 📦 deps(skills): sync superpowers 4b23529 🔧 chore(ci): merge superpowers update and sync workflow a56d75b 📦 deps(skills): sync superpowers 84bcefa 🔧 chore(ci): use ci[bot] commit author name 00a07e5 📦 deps(skills): sync superpowers 7b84daf 🐛 fix(templates): enforce main loop progress tracking 51373d7 🔧 chore(ci): automate superpowers sync workflow eaaa39c 🐛 fix(ci): prevent stale superpowers sync from restoring skills block 79755c6 📦 deps(skills): sync superpowers 836d878 📦 deps(skills): sync superpowers 8216c9f 📦 deps(skills): sync superpowers 9439505 🐛 fix(playbook): address reported repo issues git-subtree-dir: docs/standards/playbook git-subtree-split: 25d895d8b3f56624ccfe99ad7289e9eb49e0f316
375 lines
7.3 KiB
Markdown
375 lines
7.3 KiB
Markdown
# 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)
|