# 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` 的赋值分支时，不要改成省略分号的裸分支；使用前文的块式分支。
