Files
playbook/skills/tsl-syntax-reference/references/07_control_flow.md
T
cshandClaude Fable 5 2e8c46e152 📝 docs(tsl-syntax-reference): document verified facts from FAQ corpus
从 FAQ 语料(2018 篇镜像)与参考页做完 169 条逐条差集,补入 6 组此前未收录的语言
事实。每条先用 TSL.exe 实跑观察行为,再收敛成 39 条断言的回归门,在 TSLGen2、
AnalyseNG.NET、TSL 三个解释器上均通过后才写入。

- 16 页:0/0 得 nan、2/0 得 inf;nan 的 = > < >= <= 恒假而 <> 为真;nan 沿算术
  传播;nil 在乘除减里按 0 参与
- 04 页:整体赋值免声明,但对未建立的变量写单元格赋值会报「变量类型不是Array」
- 07 页:相邻两个 if 是两条独立语句都会执行;for..in 单控制变量拿到的是键不是值;
  二维数组按行推进,行本身是数组(新增 syntax-07-013)
- 21 页:子矩阵赋值语句求值为 0;t[:,:] 按位置复制不按列名对齐(新增
  syntax-21-019);| 列并时同名字符串列键由右操作数覆盖(新增 syntax-21-018)
- 13 页:select 输出列表同名列后者覆盖前者,须用 as 改名(新增 syntax-13-032)

三条 FAQ 说法实跑不复现,故未写入:12574 的括号消歧、19857 的 -2147483648
字面量限制、31474 的 :. bugfix 记录。15305/11045 的用户/公用/共享函数同名解析
优先级需要服务端多账号环境,本地无法验证,仍为已知缺口。

lexicon.json 为 16 页补两个页级 alias,使 nan/nil 语义能被自然语言查询命中。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 20:58:10 +08:00

628 lines
12 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 控制流
这一篇只收录流程控制与异常控制,不讨论金融语义。
## 本篇职责
<!-- section-id: syntax-07-001 -->
回答“`if``case``for``while``repeat``break``continue``try``raise` 这些流程结构在 TSL 里到底怎么写,哪些写法可以直接生成”。
## 核心规则
<!-- section-id: syntax-07-002 -->
- `if ... then ... else ...` 默认写成块式分支:`then begin ... end else begin ... end`
- 两个相邻的 `if ... then begin ... end` 是两条**独立**语句,两个条件都会求值、都可能执行;只有写了 `else` 才是互斥分支。
- 块式分支内部的普通语句必须用分号结尾。
- 控制流块的 `begin ... end` 后可以加分号也可以不加(语法都允许)。
- `for` 支持 `to``downto`、可选 `step`,以及 `for i, v in array` 遍历。
- `for ... in` 也可以只写一个控制变量:`for i in array do` 里的 `i` 是行下标/键,不是行值;要拿到行值就写两个控制变量。
- 二维数组按 `for i, v in array` 遍历时按行推进,`v` 本身是整行数组;要落到单元格需要再嵌套一层 `for ... in`
- 计数 `for` 的初值、终值和步长确定后,循环次数随之固定;循环体内不要给控制变量赋值。
- `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`、计时和性能分析器这类“控制流补充工具”统一放到 [14_debug_and_profiler.md](14_debug_and_profiler.md)。
## 可直接照写示例
<!-- section-id: syntax-07-003 -->
使用这些示例时遵守:
- 条件表达式、比较、布尔值和普通赋值的事实见 [06_expressions_and_operators.md](06_expressions_and_operators.md)。
- 函数里的控制流只按控制流语法处理;函数文件模型、返回值和参数规则见 [05_functions_and_calls.md](05_functions_and_calls.md)。
- `goto``debugReturn`、计时和性能分析器不在本页生成,相关事实见 [14_debug_and_profiler.md](14_debug_and_profiler.md)。
### `if`、`while`、`repeat ... until`
<!-- section-id: syntax-07-004 -->
<!-- tags: 条件判断, 循环, 判断语句, 直到满足, 反复执行 -->
代码块身份:可直接照写示例
```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
```
相邻两个 `if``if ... else` 的区别:
代码块身份:可直接照写示例
```tsl
trace := "";
if 1 then
begin
trace := trace + "A";
end
if 1 then
begin
trace := trace + "B";
end
writeLn(trace);
trace2 := "";
if 1 then
begin
trace2 := trace2 + "A";
end
else
begin
trace2 := trace2 + "B";
end
writeLn(trace2);
```
结果说明:
- 第一段输出 `AB`:两个 `if` 是两条独立语句,条件都为真所以都执行
- 第二段输出 `A``if ... else` 是一条语句,两支互斥
- 所以“多个条件里只能命中一个”必须写 `else`,靠相邻 `if` 排列不成立
代码块身份:输出片段
```text
AB
A
```
### `for` 的几种主干写法
<!-- section-id: syntax-07-005 -->
<!-- tags: 循环写法, 遍历数组, 计数循环, 倒着循环, 遍历键值 -->
最基础的递增循环:
代码块身份:可直接照写示例
```tsl
sum := 0;
for i := 0 to 2 do
sum := sum + i;
writeLn(sum);
```
代码块身份:输出片段
```text
3
```
`step` 的递增循环:
代码块身份:可直接照写示例
```tsl
sum := 0;
for i := 1 to 5 step 2 do
sum := sum + i;
writeLn(sum);
```
输出说明:
- 输出 `9`
代码块身份:输出片段
```text
9
```
`step``downto` 递减循环:
代码块身份:可直接照写示例
```tsl
sum := 0;
for i := 5 downto 1 step 2 do
sum := sum + i;
writeLn(sum);
```
输出说明:
- 输出 `9`
代码块身份:输出片段
```text
9
```
数组遍历:
代码块身份:可直接照写示例
```tsl
numbers := array(10, 20, 30);
for i, value in numbers do
writeLn(i * 100 + value);
```
输出说明:
- 依次输出 `10``120``230`
- 这说明 `for i, value in numbers` 里的 `i``0` 开始
- 遍历期间把 `numbers` 当成只读集合;需要修改时先遍历副本,或在循环结束后统一写回
代码块身份:输出片段
```text
10
120
230
```
### `for ... in` 的控制变量个数
<!-- section-id: syntax-07-013 -->
<!-- tags: 遍历只写一个变量, 键还是值, 遍历二维数组, 每行是数组, 嵌套遍历 -->
一个控制变量拿到的是下标/键,两个才拿到行值:
代码块身份:可直接照写示例
```tsl
data := array("a": 1, "b": 5, "c": 3, "d": -2);
total := 0;
for i, value in data do
total := total + value;
writeLn(total);
keys := "";
for i in data do
keys := keys + i;
writeLn(keys);
```
结果说明:
- 先输出 `7`:两个控制变量时,`value` 是行值
- 再输出 `abcd`:只写一个控制变量时,它是行下标/键,不是行值
- 所以想累加数值却只写一个控制变量,会拿到键而不是值
代码块身份:输出片段
```text
7
abcd
```
二维数组按行推进,每个 `value` 本身是一行数组:
代码块身份:可直接照写示例
```tsl
rows := array(("A": 1, "B": 2), ("A": 3, "B": 4));
colA := 0;
for i, value in rows do
colA := colA + value["A"];
writeLn(colA);
all := 0;
for i, value in rows do
for j, cell in value do
all := all + cell;
writeLn(all);
```
结果说明:
- 先输出 `4``value` 是整行数组,可以直接按列键取 `value["A"]`
- 再输出 `10`:要落到每个单元格,需要再嵌套一层 `for ... in`
- 所以二维数组不会被自动摊平成单元格序列
代码块身份:输出片段
```text
4
10
```
### `break` 与 `continue`
<!-- section-id: syntax-07-006 -->
<!-- tags: 跳出循环, 提前退出循环, 跳过本次, 结束循环 -->
`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` 语句形态
<!-- section-id: syntax-07-007 -->
<!-- tags: 多分支, 分支判断, switch 怎么写, 多条件选择 -->
普通分支:
代码块身份:可直接照写示例
```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`
<!-- section-id: syntax-07-008 -->
<!-- tags: 捕获异常, 异常处理, 出错处理, 抓错误, 异常不中断 -->
代码块身份:可直接照写示例
```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`
<!-- section-id: syntax-07-009 -->
<!-- tags: 收尾处理, 无论如何都执行, 释放资源, 清理动作 -->
正常路径:
代码块身份:可直接照写示例
```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`
<!-- section-id: syntax-07-010 -->
<!-- tags: 抛异常, 主动报错, 中断执行, 扔错误 -->
代码块身份:可直接照写示例
```tsl
writeLn("before");
raise "boom";
writeLn("after");
```
输出说明:
- 先输出 `before`
- 随后脚本报错终止,`after` 不会执行
代码块身份:输出片段
```text
before
```
## 默认生成模板
<!-- section-id: syntax-07-011 -->
最短条件分支的默认骨架如下:
代码块身份:可直接照写示例
```tsl
flag := 1;
if flag > 0 then
begin
value := 1;
end
else
begin
value := 0;
end
```
## 禁止项
<!-- section-id: syntax-07-012 -->
-`else` 前面误加分号。
- 生成没有分号的裸分支赋值,例如 `then value := 1 else ...`;带 `else` 时用块式分支。
- 以为 `try ... finally` 会吞掉异常。
- 在还没搞清表达式规则前,先把复杂业务函数塞进条件里。
- 把控制流问题和函数文件模型问题混在一起排查。
- 在计数 `for` 循环体里给控制变量赋值。
-`for ... in` 遍历期间修改被遍历数组或其中元素。
- 用相邻两个 `if ... then begin ... end` 表达互斥分支;它们是两条独立语句,需要互斥就写 `else`
-`for ... in` 只写一个控制变量却当成行值使用;单变量拿到的是下标/键。
- 以为二维数组会被 `for ... in` 摊平成单元格序列;它按行推进,行本身是数组。
代码块身份:反例 / 不可照写
```text
if flag > 0 then
value := 1;
else
value := 0;
```
上面这种写法会编译失败,因为 `else` 前面的分号会让 `if` 语句在上一行提前结束。生成带 `else` 的赋值分支时,不要改成省略分号的裸分支;使用前文的块式分支。