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

12 KiB
Raw Blame History

TSL 控制流

这一篇只收录流程控制与异常控制,不讨论金融语义。

本篇职责

回答“ifcaseforwhilerepeatbreakcontinuetryraise 这些流程结构在 TSL 里到底怎么写,哪些写法可以直接生成”。

核心规则

  • if ... then ... else ... 默认写成块式分支:then begin ... end else begin ... end
  • 两个相邻的 if ... then begin ... end 是两条独立语句,两个条件都会求值、都可能执行;只有写了 else 才是互斥分支。
  • 块式分支内部的普通语句必须用分号结尾。
  • 控制流块的 begin ... end 后可以加分号也可以不加(语法都允许)。
  • for 支持 todownto、可选 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 遍历期间,不要修改被遍历数组或其中元素。
  • whilerepeat ... until 都可直接使用;repeat 至少会先执行一轮再判断结束条件。
  • break 会跳出当前最近一层循环,continue 会跳过当前轮剩余语句。
  • case ... of ... else ... end 可作为语句形态生成;end 后可以加分号也可以不加。
  • value := case ... of ... else ... end; 可作为表达式形态生成;表达式形态赋值语句本身要用分号结尾。
  • case 分支标签支持逗号并列和 to 区间。
  • try ... except ... end 可以捕获 raise 产生的错误,并继续执行后续语句。
  • exceptObject.errInfoexcept 块中可读,能拿到当前错误信息。
  • exceptObject.errLineexceptObject.errNoexcept 块中也可读。
  • try ... finally ... end 无论是否报错,都会先执行 finally;如果没有 except 吞掉错误,脚本仍会在 finally 之后报错终止。
  • raise "message" 是最小抛错写法。
  • gotodebugReturndebugRunEnv、计时和性能分析器这类“控制流补充工具”统一放到 14_debug_and_profiler.md

可直接照写示例

使用这些示例时遵守:

ifwhilerepeat ... until

代码块身份:可直接照写示例

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);

代码块身份:输出片段

1
0

相邻两个 ifif ... else 的区别:

代码块身份:可直接照写示例

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 是两条独立语句,条件都为真所以都执行
  • 第二段输出 Aif ... else 是一条语句,两支互斥
  • 所以“多个条件里只能命中一个”必须写 else,靠相邻 if 排列不成立

代码块身份:输出片段

AB
A

for 的几种主干写法

最基础的递增循环:

代码块身份:可直接照写示例

sum := 0;
for i := 0 to 2 do
    sum := sum + i;
writeLn(sum);

代码块身份:输出片段

3

step 的递增循环:

代码块身份:可直接照写示例

sum := 0;
for i := 1 to 5 step 2 do
    sum := sum + i;
writeLn(sum);

输出说明:

  • 输出 9

代码块身份:输出片段

9

stepdownto 递减循环:

代码块身份:可直接照写示例

sum := 0;
for i := 5 downto 1 step 2 do
    sum := sum + i;
writeLn(sum);

输出说明:

  • 输出 9

代码块身份:输出片段

9

数组遍历:

代码块身份:可直接照写示例

numbers := array(10, 20, 30);
for i, value in numbers do
    writeLn(i * 100 + value);

输出说明:

  • 依次输出 10120230
  • 这说明 for i, value in numbers 里的 i0 开始
  • 遍历期间把 numbers 当成只读集合;需要修改时先遍历副本,或在循环结束后统一写回

代码块身份:输出片段

10
120
230

for ... in 的控制变量个数

一个控制变量拿到的是下标/键,两个才拿到行值:

代码块身份:可直接照写示例

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:只写一个控制变量时,它是行下标/键,不是行值
  • 所以想累加数值却只写一个控制变量,会拿到键而不是值

代码块身份:输出片段

7
abcd

二维数组按行推进,每个 value 本身是一行数组:

代码块身份:可直接照写示例

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);

结果说明:

  • 先输出 4value 是整行数组,可以直接按列键取 value["A"]
  • 再输出 10:要落到每个单元格,需要再嵌套一层 for ... in
  • 所以二维数组不会被自动摊平成单元格序列

代码块身份:输出片段

4
10

breakcontinue

break

代码块身份:可直接照写示例

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

代码块身份:输出片段

6
4

continue

代码块身份:可直接照写示例

i := 0;
sum := 0;
while i < 4 do
begin
    i := i + 1;
    if i = 2 then
        continue;
    sum := sum + i;
end
writeLn(sum);

输出说明:

  • 输出 8

代码块身份:输出片段

8

case 语句形态

普通分支:

代码块身份:可直接照写示例

a := 2;
case a of
1:
    writeLn("one");
2:
    writeLn("two");
else
    writeLn("other");
end

输出说明:

  • 输出 two

代码块身份:输出片段

two

并列标签与区间:

代码块身份:可直接照写示例

a := 4;
case a of
1, 2:
    writeLn("small");
3 to 5:
    writeLn("mid");
else
    writeLn("other");
end

输出说明:

  • 输出 mid

代码块身份:输出片段

mid

case 表达式形态:

代码块身份:可直接照写示例

a := 3;
label_value := case a of
1, 2:
    "small";
3, 4:
    "mid";
else
    "other";
end;
writeLn(label_value);

输出说明:

  • 输出 mid

代码块身份:输出片段

mid

说明:

  • 表达式形态可以放在赋值右侧。
  • 表达式形态的每个分支只写单条表达式/单条语句,不写 begin ... end 语句段。
  • 赋值语句整体以 end; 收尾。

try ... except

代码块身份:可直接照写示例

writeLn("before");
try
    raise "boom";
except
    writeLn("caught");
    writeLn(exceptObject.errInfo);
end
writeLn("after");

输出说明:

  • 先输出 before
  • 再输出 caught
  • exceptObject.errInfo 输出包含 raise: boom 的错误信息
  • 最后继续输出 after

代码块身份:输出片段

before
caught
raise: boom
after

exceptObject 的扩展字段:

代码块身份:可直接照写示例

try
    raise "boom";
except
    writeLn(exceptObject.errLine);
    writeLn(exceptObject.errNo);
end

输出说明:

  • exceptObject.errLine 输出 4
  • exceptObject.errNo 输出 2
  • 说明异常对象除了 errInfo 以外,也能直接提供出错行号和错误号

代码块身份:输出片段

4
2

try ... finally

正常路径:

代码块身份:可直接照写示例

writeLn("before");
try
    writeLn("body");
finally
    writeLn("finally");
end
writeLn("after");

输出说明:

  • 依次输出 beforebodyfinallyafter

代码块身份:输出片段

before
body
finally
after

报错路径:

代码块身份:可直接照写示例

writeLn("before");
try
    writeLn("body");
    raise "boom";
finally
    writeLn("finally");
end
writeLn("after");

输出说明:

  • 先输出 before
  • 再输出 body
  • 然后仍会输出 finally
  • 随后脚本报错终止,after 不会执行

代码块身份:输出片段

before
body
finally

raise

代码块身份:可直接照写示例

writeLn("before");
raise "boom";
writeLn("after");

输出说明:

  • 先输出 before
  • 随后脚本报错终止,after 不会执行

代码块身份:输出片段

before

默认生成模板

最短条件分支的默认骨架如下:

代码块身份:可直接照写示例

flag := 1;
if flag > 0 then
begin
    value := 1;
end
else
begin
    value := 0;
end

禁止项

  • else 前面误加分号。
  • 生成没有分号的裸分支赋值,例如 then value := 1 else ...;带 else 时用块式分支。
  • 以为 try ... finally 会吞掉异常。
  • 在还没搞清表达式规则前,先把复杂业务函数塞进条件里。
  • 把控制流问题和函数文件模型问题混在一起排查。
  • 在计数 for 循环体里给控制变量赋值。
  • for ... in 遍历期间修改被遍历数组或其中元素。
  • 用相邻两个 if ... then begin ... end 表达互斥分支;它们是两条独立语句,需要互斥就写 else
  • for ... in 只写一个控制变量却当成行值使用;单变量拿到的是下标/键。
  • 以为二维数组会被 for ... in 摊平成单元格序列;它按行推进,行本身是数组。

代码块身份:反例 / 不可照写

if flag > 0 then
    value := 1;
else
    value := 0;

上面这种写法会编译失败,因为 else 前面的分号会让 if 语句在上一行提前结束。生成带 else 的赋值分支时,不要改成省略分号的裸分支;使用前文的块式分支。