# 02 控制流与异常

本章汇总流程控制、错误控制与调试相关语句。

## 目录

- [02 控制流与异常](#02-控制流与异常)
  - [目录](#目录)
  - [流程控制语句](#流程控制语句)
    - [条件语句](#条件语句)
      - [IF](#if)
      - [IF 表达式](#if-表达式)
      - [CASE](#case)
    - [循环语句](#循环语句)
      - [WHILE](#while)
      - [REPEAT](#repeat)
      - [FOR](#for)
      - [BREAK](#break)
      - [CONTINUE](#continue)
    - [GOTO](#goto)
    - [错误控制，以及调试语句](#错误控制以及调试语句)
      - [异常处理 Try Except/Finally](#异常处理-try-exceptfinally)
      - [ExceptObject 异常对象](#exceptobject-异常对象)
      - [RAISE](#raise)
      - [DEBUGRETURN](#debugreturn)
      - [DebugRunEnv 与 DebugRunEnvDo](#debugrunenv-与-debugrunenvdo)
      - [MTIC,MTOC 计算运算时间](#mticmtoc-计算运算时间)
      - [SetProfiler,GetProfilerInfo 优化信息](#setprofilergetprofilerinfo-优化信息)
      - [调用信息与代码行号](#调用信息与代码行号)
    - [函数的返回和退出](#函数的返回和退出)

## 流程控制语句

### 条件语句

#### IF

if 语句是由一个布尔表达式和两个供选择的操作序列组成。运行时根据布尔表达式求值结果，选取其中之一的操作序列执行。有两种形式的 if 语句：

```tsl
if <布尔表达式> then <语句> ;

if <布尔表达式> then <语句1>
else <语句2> ;
```

当布尔表达式的值为真，执行 then 后面的语句；当值为假时则有两种情况：要么什么也不做，要么执行 else 后面的语句。

注意：

else 前面没有分号，因为分号是两个语句之间的分隔符，而 else 并非语句。如果在该处添了分号，则远程服务器在编译的时候就会认为 if 语句到此结束，而把 else 当作另一句的开头，这样就会输出出错信息。

语句可以是一条语句或是一组语句，如果是一组语句时，这组语句必须使用 Begin …
end 标识符来限定，写成复合语句。在用 if 语句连续嵌套时，如果你插入适量的复合语句，有利于程序的阅读和理解。

例 2：求 y=f(x),当 x>0 时，y=1，当 x=0 时，y=0，当 x<0 时，y=-1。

```tsl
function IfExample(x);
begin
    if x > 0 then
        y := 1;
    else if x = 0 then
        y := 0;
    else
        y := -1;
    return y;
end;
```

例 3：当 x>0 时候，计算 x*x，并且输出 x*x，否则输出 0。

```tsl
function IfExample2(x);
begin
    if x >= 0 then
    begin
        x1 := x * x;
        return x1;
    end
    else return 0;
end;
```

注意：当 if 语句嵌套时，TSL 约定 else 总是和最近的一个 if 配对。

#### IF 表达式

if 表达式是一种条件表达式，它根据条件的真假来返回不同的值。它与 if 语句不同：

if 语句：是一种控制流语句，用于决定是否执行某段代码块，本身不返回值。

if 表达式：会计算一个结果，这个结果可以赋值给变量、作为函数参数或在其他表达式中使用。功能类似三元运算符，但 if 表达式更通用，可读性更高

其基本形式通常如下：

if 条件 then 值1 else 值2

例如 if a>1 then 2 else 1，如果 a 大于 1，整个表达式的结果就是 2，否则是 1。

if 表达式必须存在 else 部分，主要是为了确保表达式始终有确定的返回值。如果没有 else，当条件为假时，表达式的返回值将是不确定的。

注：仅 2025-08-27 以后的语言版本支持此功能

示例：

```tsl
ret := if x > 0 then x * x else 0;
return ret;
```

当 x>0，返回 x\*x；x<=0 时，返回 0。

多个分支：

```tsl
ret := if x > 0 then x * x else if x < 0 then - (x * x) else 0;
return ret;
```

当 x>0，返回 x*x；x<0 时，返回-x*x；x=0，返回 0。

#### CASE

多分支条件语句，Case of

语法一：普通语法。

```tsl
case <Expression> of
    <情况标号表 1>: 语句 1;
    <情况标号表 2>: 语句 2;
    ...
    <情况标号表 N>: 语句 N;
    [else 例外语句;]
end;
```

情况标号表的语法为：

```tsl
case 区间 1[, case 区间 2 .. case 区间 N]
区间开始值 [to 区间结束值]
```

如果没有 TO 语句，则结束值和开始值相同。

例：

```tsl
function CaseExample(Age);
begin
    case Age of
        0: Writeln("婴儿");
        1, 2: Writeln("婴幼儿");
        3 to 6: Writeln("幼儿");
        7 to 14: Writeln("少年");
        15 to 17: Writeln("青少年");
    else
        Writeln("成年");
    end;
end;
```

语法二：支持 Case 表达式，在该种情况下，分支语句不支持语句段，只能是单语句表达式。

```tsl
B := case <Expression> of
    <情况标号表 1> : 表达式 1;
    <情况标号表 2> : 表达式 2;
```

…（其它的与普通用法一致）

范例：

范例一：

```tsl
a := 3;
b := case a of
    1, 2: "1/2";
    3, 4: "3/4";
else "OTHER";
end;
return b;
```

结果：3/4

范例二：

```tsl
a := 3;
b := case a of
    1, 2: echo "1/2";
    3, 4: echo "3/4";
else "OTHER";
end;
return b;
```

结果：0。打印窗口：3/4

范例三：表达式的用法

```tsl
b := @case a of
    1, 2: "1/2";
    3, 4: "3/4";
else "OTHER";
end;
a := 2;
return eval(b);
```

结果：1/2

### 循环语句

当需要重复执行一条或是一组语句时，可以使用循环控制语句。TSL 中的循环控制语句有 While 语句和 For 语句。

#### WHILE

while 语句用于"当满足某一条件时重复执行语句"的情况。while 语句的语法格式：

while 布尔表达式 do 语句；

循环结束条件在进入循环体之前测试，若最初的测试值为 false，则根本不进入循环体。为了能使 while 重复能终止，循环体中一定要有影响布尔表达式的操作，否则该循就是一个死循环。

说明：

语句可以是一条语句或是一组语句，如果是一组语句时，这组语句必须使用 Begin …
end 标识符来限定，写成复合语句。

例 4：计算从 0 到某个数之间的和。

```tsl
function sums(limit);
begin
    sum := 0;
    num := 0;
    while num <= limit do
    begin
        sum := sum + num;
        num++;
    end;
    return sum;
end;
```

#### REPEAT

repeat 语句用于”重复执行语句直到满足某一条件”的情况。repeat 语句的语法格式：

```tsl
repeat
    // 语句段;
until 布尔表达式;
```

说明：

repeat 与 while 不同之处有几点：

1,repeat 先做后判断是否结束，while 先判断后做，也就是说 repeat 至少会做一次；

2,repeat 的判断条件是结束条件，而 while 的判定条件是开始做的条件；

3,repeat 和 util 之间可以有语句段，不需要 begin
end 来限定，而 while 由于没有结束的特殊标识符，因此当使用语句段的时候必须用 begin
end 来约束。

例 5：求第一个阶乘超过指定值的值

```tsl
function MinMultiValue(limit);
begin
    multi := 1;
    value := 1;
    repeat
        multi := multi * value;
        value++;
    until multi > limit;
    return value;
end;
```

#### FOR

```tsl
for 语句用来描述已知重复次数的循环结构。for 语句有三种形式：
(1) for 控制变量 := 初值 to 终值 [step 步长] do 语句；
(2) for 控制变量 := 初值 downto 终值 [step 步长] do 语句；
(3) for 控制变量 1，控制变量 2 in 数组 do 语句；
```

第一种形式的 for 语句是递增循环。

首先将初值赋给控制变量，接着判断控制变量的值是否小于或等于终值，若是，则执行循环体，在执行了循环体之后，自动将控制变量的值该为它的后继值，并重新判断是否小于或等于终值。当控制变量的值大于终值时，退出 for 循环，执行 for 语句之后的语句。

可通过 step N 方式指定递增步长，可省，默认为 1。

第二种形式的 for 语句是递减循环。

首先将初值赋给控制变量，接着判断控制变量的值是否大于或等于终值，若是，则执行循环体，在执行了循环体之后，自动将控制变量的值该为它的前趋值，并重新判断是否大于或等于终值。当控制变量的值小于终值时，退出 for 循环，执行 for 语句之后的语句。

可通过 step N 方式指定递减步长，可省，默认为 1。

注意：for 语句中，当初值、终值、步长确定后，重复的次数就确定不变了，并且控制变量在重复语句内不能施加任何赋值操作。

例如：计算 1+2+3+……+99+100 的值

```tsl
function PlusFor();
begin
    sum := 0;
    for i := 1 to 100 do sum := sum + i; // 缺省步长，默认步长为 1
    return sum;
end;
```

例如：计算 1+3+5+……+99 的值

```tsl
function PlusFor2();
begin
    sum := 0;
    for i := 1 to 100 step 2 do sum := sum + i;
    return sum;
end;
```

第三种形式的 for 语句是直接对数组进行遍历

对数组中的每一行（第一维）进行遍历，当前行的下标存放在第一个控制变量中，该行对应的值存放在第二个控制变量中。从第一行开始，将行标与当前行的值分别赋值给控制变量 1 与控制变量 2 后，执行循环体，在执行了循环体之后，自动将 2 个控制变量的值赋值为下一行的下标及该行值，当遍历完最后一行之后，退出 for 循环，执行 for 语句之后的语句。

for … in 遍历的用法说明

语法：For i,v IN TArray DO 语句;说明：对数据的遍历。

其中，i：控制变量 1,获取当前循环中数组第一维的下标值 v：控制变量 2，对应当前循环中第一维度的值

TArray：需要被遍历的数组。

注 1：二维及多维数组可当作一维处理，此时的控制变量 2 的值则可能是一个数组。

注 2：在此过程中，不可更改一维数组的值，也不可对该数组中的任何元素进行赋值操作，对在循环过程中不可对循环数组 TArray 进行变更操作。

适应场景：对于非数字下标的数组，处理比较方便，且效率高

范例一：一维数组的应用

```tsl
data := array('a': 1, 'b': 5, 'c': 3, 'd': -2);
s := 0;
for i, v in data do s += v;
return s;
```

返回：7

范例二：二维数组的应用

```tsl
data := rand(array('a', 'b', 'c'), array('AA', 'BB', 'CC', 'DD'));
s := 0;
t := 1;
for i, v in data do // data 是二维数组，所以第一维中 v 的值是当前行的一维数组
    for j, v1 in v do
    begin
        s += v1;
        t *= v1;
    end;
return array(s, t);
```

返回：array(12,1)

#### BREAK

在执行 WHILE 和 FOR 以及 REPEAT
until 循环语句时，可以用 break 语句随时从当前循环的语句段中跳出来，并继续执行循环语句后面的语句。

注意：Break 语句只是从当前的语句循环中跳出来，如果要从多个嵌套的循环语句中跳出，则需要通过多个对应的 Break 语句来完成。

例 7：我们用 While 语句和 Break 语句重新来例 5 中的 1+2+3+……+99+100 值

```tsl
function PlusWhile();
begin
    sum := 0;
    i := 0;
    while true do
    begin
        i++;
        if i > 100 then break;
        sum := sum + i;
    end;
    return sum; // break 后执行的第一行语句
end;
```

#### CONTINUE

CONTINUE 语句和 BREAK 语句一样，都可以改变 WHILE 循环语句和 FOR 循环语句以及 REPEAT
until 的执行顺序。

BREAK 是强制地从一个循环语句中跳出来,提前结束循环，而 CONTINUE 语句则强制地结束当前循环开始进入下一次循环。

如：

```tsl
while true do
begin
    i++;
    if i = 100 then continue; // 跳过 100
    if i >= 1000 then break; // 到 1000 结束
end;
```

### GOTO

几乎所有的分支流程控制语句都指令跳转有关，只是绝大多数情况下是有条件跳转,GOTO 是无条件跳转语句，其规则是使用 label 定义标号，使用 goto 可以跳转到指定的标号。

一个 GOTO 的案例：

```tsl
for i := 0 to length(data) - 1 do
begin
    for j := 0 to length(data[i]) - 1 do
    begin
        if data[i][j] = target then
        begin
            goto finded;
        end;
    end;
end;
label finded;
// 在二维数组中查找，找到即结束
```

goto 有一个特性，就是只能从内层往外层跳转（且不能跨越函数）

### 错误控制，以及调试语句

#### 异常处理 Try Except/Finally

某些函数在执行的过程中可能会自动抛出异常，或者被手动 Raise 抛出异常，这个时候如果没有异常处理运行就会终止。

使用异常处理则可以保护程序继续执行，并可以对异常进行相应的处理。异常的信息可以由 ExceptObject 对象获得，异常处理使用如下模式：

```tsl
try
    // 被保护的程序执行段
except
    // 异常处理程序段
end;
```

例如：

```tsl
try
    I := StrToInt(S); // 当 S 不能转换为整数时会产生异常
except
    I := 0; // 当发生异常时设置 I 为 0
    Writeln(ExceptObject.ErrInfo);
end;
```

对于某个程序段可能出现中途返回或者退出，或者中途被异常中断，而某些代码必需要在其后执行的，则采用如下模式：

```tsl
try
    // 被保护的程序执行段
finally
    // 保证执行的处理程序段，即便Try Finally之间的语句有返回或者异常产生。
end;
```

注：try...Except...end 可以使程序在报错时继续向下运行，即主程序不终止。

try...finally...end 则是该报错时就会报错，即发生错误时程序会报错且终止，只是在中断前会执行完 finally 中的命令行。

#### ExceptObject 异常对象

在 Except 块中，可以用 ExceptObject 获得当前的异常信息。

ExceptObject 是一个异常对象，包括以下几个成员：

ExceptObject. ErrInfo 获得错误的信息串

ExceptObject. ErrLine 错误的行号

ExceptObject. ErrNo 错误号

例如：

```tsl
a := 100;
try
    a := 1 + 'a';
except
    echo ExceptObject.ErrInfo;
end;
return a;
```

打印结果：

function:NoName501:line 11:instruction:+: Addition instruction error,operand
type error

#### RAISE

主动抛出运行时异常，会引发程序出错并终止运行，RAISE 后跟随一个字符串，该字符串为出错返回的错误信息。

如：

```tsl
A := -1;
if a < 0 then raise “a不能小于0”;
```

运行时报错如：

#### DEBUGRETURN

调试返回，后面跟返回值，可在任何地方直接将结果返回，而不是象 RETURN 一样返回到上一级别，这有助于用户调试使用。

如下面示例，返回为 3 而不是 4：

```tsl
A := abcd(3);
return A + 1;
function abcd(bb);
begin
    debugreturn
    bb;
end;
```

#### DebugRunEnv 与 DebugRunEnvDo

DebugRunEnv(0)与 DebugRunEnv(1)

DebugRunEnv(0)可以将所有的变量内容递交到客户端的调试窗口

DebugRunEnv(1)可以将变量以及系统参数的内容递交到客户端的调试窗口

DebugRunEnvDo FunctionXX(….)

运行完指定的函数以后返回该函数最后的变量结果

#### MTIC,MTOC 计算运算时间

可以通过 MTIC 与 MTOC 记录一段程序运行的时间。

一般使用：以上代码可以计算所耗费的秒数

```tsl
MTIC
;
A := 0;
for i := 0 to 99999 do &#61607; A++;
return
MTOC
;
```

扩展使用：同时统计多段程序的运行时间

默认 MTOC 和上次 MTIC 匹配，但是也可以指定某个 MTIC 的返回来计算时间

```tsl
T1 :=
MTIC
;
for i := 0 to 9999 do A++;
TE1 :=
MTOC(T1)
;
MTIC;
for j := 0 to 9999 do A++;
TE2 :=
MTOC(T1)
;
return array(TE1, TE2,
MTOC
);
```

返回结果中：TE1 为第一段循环运行的时间，TE2 为两段循环运行的时间，第三个值为第二段循环运行的时间。

#### SetProfiler,GetProfilerInfo 优化信息

在程序中可通过指定 SetProfiler 指定运行时计算用户函数，系统函数，以及运算指令的耗费时间，最后通过 GetProfilerInfo()获得这些信息，如果不用 GetProfilerInfo,返回时会自动新建 Profiler 窗口来显示这些信息，客户端可以在运行时指定优化信息系统参数。

函数具体用法及优化信息结构可参考：SetProfiler、GetProfilerInfo

#### 调用信息与代码行号

通过关键字\_\_stack_frame 获得调用的堆栈的函数名以及行号

通过关键字**line**获得当前所在的行号

### 函数的返回和退出

参见函数返回以及退出
