973 lines
21 KiB
Markdown
973 lines
21 KiB
Markdown
# Functions And Calls
|
||
|
||
文档类型:语法主线
|
||
是否可直接用于生成代码:是
|
||
是否含已验证可执行示例:是
|
||
是否含已验证反例:是
|
||
遇到不确定时跳转到:[03_core_model.md](03_core_model.md)(优先)、[10_units_and_scope.md](10_units_and_scope.md)、[21_external_calls_and_threads.md](21_external_calls_and_threads.md)
|
||
|
||
手册位置:第 6 篇,共 32 篇。上一篇:[05_variables_and_constants.md](05_variables_and_constants.md)。下一篇:[07_expressions_and_operators.md](07_expressions_and_operators.md)。
|
||
|
||
这一篇只负责 `function` / `procedure` 的定义、调用、参数传递和值返回,不延伸到业务函数库。
|
||
|
||
## 这一篇解决什么问题
|
||
|
||
回答“如何正确声明 `function` 和 `procedure`、如何组织主函数和子函数、怎样使用参数修饰、默认参数与可变参数,以及哪些混写方式会直接编译失败”。
|
||
|
||
## 必须记住的规则
|
||
|
||
- 最稳妥的函数骨架仍然是 `function Name(...); begin ... end;`。
|
||
- 不需要返回值时,可以改用 `procedure Name(...); begin ... end;`。
|
||
- 在文件模型层,`function` 和 `procedure` 归同一类顶层外形;见 [03_core_model.md](03_core_model.md)。
|
||
- 当前解释器接受省略函数头后的分号,但文档默认仍保留这个分号。
|
||
- 一个函数定义体里可以同时出现主函数和子函数。
|
||
- 函数支持参数类型注解和返回值类型注解。
|
||
- 不带类型注解时,多个参数用逗号分隔。
|
||
- 带类型注解时,多个参数用分号分隔。
|
||
- 当前解释器接受 `const` 与 `var` 形参修饰。
|
||
- 当前解释器的运行时默认行为是:未修饰参数可以写回调用方;这是运行时默认,不是语言规范保证。
|
||
- `{$VarByRef-}` 会把未修饰参数切换成按值传递;`var` 形参仍保持引用语义。
|
||
- 在 `{$VarByRef-}` 下,调用时可以用 `in` / `out` 前缀逐个参数覆盖默认传递方式。
|
||
- 如果不确定当前任务是否需要写回语义,优先显式用 `const` 形参,或先切到 `{$VarByRef-}`;不要依赖未修饰参数的运行时默认行为。
|
||
- `return expr;` 会直接返回当前函数结果。
|
||
- `exit;` 会立即结束当前函数;在当前最小样例里,如果此前没有写入返回结果,调用方观察到的是默认值 `0`。
|
||
- 调用时支持命名参数,写法是 `name: value`。
|
||
- 命名参数当前也支持 `call(...)` 这类按函数名或函数指针转调的模型。
|
||
- 一旦某次调用里开始使用命名参数,后面的参数就不能再退回位置参数。
|
||
- 对二进制函数 / 系统函数直接使用命名参数,当前会报 `named parameter mode can't support here`;这类函数要先用 TSL 再封一层。
|
||
- 函数参数支持默认值。
|
||
- 普通函数的默认值规则不要直接等同到 `unit interface` 声明;跨 `unit` 的默认参数边界见这一篇后面的对照例子,以及 [10_units_and_scope.md](10_units_and_scope.md)。
|
||
- 当前解释器支持尾部 `...` 形式的可变参数。
|
||
- 在可变参数函数体里,`Params`、`ParamCount`、`RealParamCount` 都已验证可用。
|
||
- 可变参数组可以通过 `...` 转发给另一个函数调用。
|
||
- 可变参数组也可以通过 `call(fc, ...)`、`##fc(...)`、`invoke(obj, name, 0, ...)` 转发。
|
||
- 当前解释器接受 `a := function(...) begin ... end;` 这种匿名函数写法。
|
||
- 匿名函数可以直接作为参数传入另一个函数。
|
||
- 当前解释器接受 `ThisFunction(FuncName)` 把已知函数绑定成函数值。
|
||
- 当前解释器里,匿名函数和函数指针的稳定调用方式仍是 `call(f, ...)` 或 `##f(...)`。
|
||
- 当前解释器没有通过 `f(...)` 这种“函数变量直接调用”写法;无论 `f` 是匿名函数、`FindFunction(...)` 还是 `ThisFunction(...)` 返回的函数指针,都不要默认写成直调。
|
||
- 当前解释器接受 `::FuncName(...)` 指向全局/系统函数,用来绕过当前作用域里的同名局部函数。
|
||
- `external`、`MakeInstance` 和线程调用统一移到 [21_external_calls_and_threads.md](21_external_calls_and_threads.md)。
|
||
- 不要把顶层函数定义和松散语句混在同一个文件模型里。
|
||
|
||
## 已验证语法
|
||
|
||
### 基础函数 / 过程骨架
|
||
|
||
最短函数骨架:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
function Add(a, b);
|
||
begin
|
||
return a + b;
|
||
end;
|
||
```
|
||
|
||
主函数加子函数:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
function MultiFunc();
|
||
begin
|
||
return Twice(3);
|
||
end;
|
||
|
||
function Twice(x);
|
||
begin
|
||
return x * 2;
|
||
end;
|
||
```
|
||
|
||
函数头省略分号在当前解释器里也可编译:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
function MissingSemi()
|
||
begin
|
||
return 1;
|
||
end;
|
||
```
|
||
|
||
最短 `procedure` 骨架:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
procedure LogDone();
|
||
begin
|
||
end;
|
||
```
|
||
|
||
`procedure` 写回参数的最小运行样例:
|
||
|
||
这是验证样例外壳,不作为正式顶层模型归类依据。
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
procedure Bump(var x);
|
||
begin
|
||
x := x + 1;
|
||
end;
|
||
begin
|
||
a := 1;
|
||
Bump(a);
|
||
WriteLn(a);
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `Bump(a)` 后输出 `2`
|
||
|
||
### 签名增强
|
||
|
||
带参数类型和返回值类型:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
function Demo(a: integer): integer;
|
||
begin
|
||
return a;
|
||
end;
|
||
```
|
||
|
||
带类型时的多参数分隔:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
function Demo(a: integer; b: integer);
|
||
begin
|
||
return a + b;
|
||
end;
|
||
```
|
||
|
||
类型名可以写成更偏说明性的名字:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
function Demo(a: input_value; b: handler): result_type;
|
||
begin
|
||
return a;
|
||
end;
|
||
```
|
||
|
||
参数修饰:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
function ReadConst(const x);
|
||
begin
|
||
return x + 1;
|
||
end;
|
||
procedure SetVar(var x);
|
||
begin
|
||
x := x + 5;
|
||
end;
|
||
begin
|
||
a := 10;
|
||
WriteLn(ReadConst(a));
|
||
SetVar(a);
|
||
WriteLn(a);
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `ReadConst(a)` 输出 `11`
|
||
- `SetVar(a)` 之后,`a` 输出 `15`
|
||
- 直接给 `const` 形参赋值会编译失败
|
||
|
||
### 参数传递方式
|
||
|
||
默认参数写回调用方:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
function TouchDefault(a);
|
||
begin
|
||
a := 9;
|
||
end;
|
||
begin
|
||
x := 1;
|
||
TouchDefault(x);
|
||
WriteLn(x);
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `TouchDefault(x)` 之后,`x` 输出 `9`
|
||
|
||
`{$VarByRef-}` 与 `var` 形参:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
function TouchDefault(a);
|
||
begin
|
||
a := 9;
|
||
end;
|
||
{$VarByRef-}
|
||
function TouchValue(a);
|
||
begin
|
||
a := 8;
|
||
end;
|
||
function TouchForcedVar(var a);
|
||
begin
|
||
a := 7;
|
||
end;
|
||
{$VarByRef+}
|
||
begin
|
||
x := 1;
|
||
TouchDefault(x);
|
||
WriteLn(x);
|
||
y := 1;
|
||
TouchValue(y);
|
||
WriteLn(y);
|
||
z := 1;
|
||
TouchForcedVar(z);
|
||
WriteLn(z);
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 默认模式下,`TouchDefault(x)` 后 `x` 输出 `9`
|
||
- `{$VarByRef-}` 下,未修饰参数版本 `TouchValue(y)` 之后,`y` 仍输出 `1`
|
||
- `{$VarByRef-}` 下,`var` 形参版本 `TouchForcedVar(z)` 之后,`z` 输出 `7`
|
||
|
||
`in` / `out` 调用前缀:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
{$VarByRef-}
|
||
procedure Touch3(a, b, c);
|
||
begin
|
||
a := 1;
|
||
b := 2;
|
||
c := 3;
|
||
end;
|
||
begin
|
||
a := 0;
|
||
b := 0;
|
||
c := 0;
|
||
Touch3(a, b, c);
|
||
WriteLn(a);
|
||
WriteLn(b);
|
||
WriteLn(c);
|
||
Touch3(in a, out b, c);
|
||
WriteLn(a);
|
||
WriteLn(b);
|
||
WriteLn(c);
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 在 `{$VarByRef-}` 下,直接调用 `Touch3(a, b, c)` 后依次输出 `0`、`0`、`0`
|
||
- 同样在 `{$VarByRef-}` 下,`Touch3(in a, out b, c)` 后依次输出 `0`、`2`、`0`
|
||
- 说明 `in` / `out` 可以在调用点逐个参数覆盖当前默认传递方式
|
||
|
||
### `return` 与 `exit`
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
function Demo(x);
|
||
begin
|
||
if x > 0 then
|
||
exit;
|
||
return 99;
|
||
end;
|
||
begin
|
||
WriteLn(Demo(1));
|
||
WriteLn(Demo(0));
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `Demo(1)` 输出 `0`
|
||
- `Demo(0)` 输出 `99`
|
||
- 说明 `exit;` 会立即结束当前函数体
|
||
- 在这个最小例子里,因为 `exit;` 之前没有写入返回结果,调用方观察到的是默认值 `0`
|
||
|
||
### 调用增强
|
||
|
||
命名参数调用:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
function NamedArgsDemo();
|
||
begin
|
||
return Pack(a: 1, b: 2);
|
||
end;
|
||
|
||
function Pack(a, b);
|
||
begin
|
||
return a * 10 + b;
|
||
end;
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `Pack(a: 1, b: 2)` 返回 `12`
|
||
- `Pack(b: 2, a: 1)` 返回 `12`
|
||
- `Pack(1, b: 2)` 返回 `12`
|
||
|
||
跳过中间参数时,未命中的参数当前保持 `nil`:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
function TestFunc(a, b, c);
|
||
begin
|
||
WriteLn(IfNil(b));
|
||
return 0;
|
||
end;
|
||
begin
|
||
TestFunc(1, c: 3);
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 输出 `1`
|
||
- 说明 `TestFunc(1, c: 3)` 这种调用里,中间参数 `b` 当前会保持 `nil`
|
||
|
||
通过 `call(...)` 也支持命名参数:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
function TestFunc(a, b, c);
|
||
begin
|
||
return a * 100 + b * 10 + c;
|
||
end;
|
||
begin
|
||
WriteLn(call("TestFunc", a: 1, c: 2, b: 3));
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 输出 `132`
|
||
- 说明 `call(...)` 当前也支持按参数名传值
|
||
- 说明命名参数传入后不再按位置解释,而是按名字绑定到形参
|
||
|
||
命名参数可以调换顺序:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
function NamedArgsDemo();
|
||
begin
|
||
return Pack(b: 2, a: 1);
|
||
end;
|
||
|
||
function Pack(a, b);
|
||
begin
|
||
return a * 10 + b;
|
||
end;
|
||
```
|
||
|
||
命名参数也可以和位置参数混用:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
function NamedArgsDemo();
|
||
begin
|
||
return Pack(1, b: 2);
|
||
end;
|
||
|
||
function Pack(a, b);
|
||
begin
|
||
return a * 10 + b;
|
||
end;
|
||
```
|
||
|
||
### 默认参数
|
||
|
||
默认值参数:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
function AddOne(a = 1);
|
||
begin
|
||
return a + 1;
|
||
end;
|
||
```
|
||
|
||
带类型时也支持默认值:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
function TypedAdd(a: integer = 1): integer;
|
||
begin
|
||
return a + 1;
|
||
end;
|
||
```
|
||
|
||
多个参数时,后面的参数可以带默认值:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
function Pack(a, b = 2);
|
||
begin
|
||
return a * 10 + b;
|
||
end;
|
||
```
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
function Pack(a: integer; b: integer = 2): integer;
|
||
begin
|
||
return a * 10 + b;
|
||
end;
|
||
```
|
||
|
||
默认值也可以写成表达式:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
function ExprDefault(a = 1 + 2);
|
||
begin
|
||
return a;
|
||
end;
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `AddOne()` 返回 `2`
|
||
- `AddOne(5)` 返回 `6`
|
||
- `TypedAdd()` 返回 `2`
|
||
- `TypedAdd(5)` 返回 `6`
|
||
- `Pack(1)` 返回 `12`
|
||
- `Pack(a: 1)` 返回 `12`
|
||
- `ExprDefault()` 返回 `3`
|
||
|
||
`unit interface` 声明下的默认参数要单独看:
|
||
|
||
以下是不可照写的对比:普通函数默认参数能跑通,不等于跨 `unit` 声明边界也同样可靠。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
// P.tsf
|
||
function P(a = 5, b = 6);
|
||
begin
|
||
return a + b;
|
||
end;
|
||
|
||
// UnitLiteral.tsf
|
||
unit UnitLiteral;
|
||
interface
|
||
function H(a = 5);
|
||
implementation
|
||
function H(a);
|
||
begin
|
||
return a;
|
||
end;
|
||
end.
|
||
|
||
// UnitConst.tsf
|
||
unit UnitConst;
|
||
interface
|
||
const CS = 888;
|
||
function F(a, b = 100, c = CS);
|
||
implementation
|
||
function F(a, b, c);
|
||
begin
|
||
return a + b + c;
|
||
end;
|
||
end.
|
||
|
||
// main.tsl
|
||
program test;
|
||
uses UnitLiteral, UnitConst;
|
||
begin
|
||
WriteLn(P());
|
||
WriteLn(H());
|
||
WriteLn(F(1));
|
||
WriteLn(F(1, 2));
|
||
end.
|
||
|
||
// command
|
||
tsl .\main.tsl -LIBPATH "D:\path\to\dir\"
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 普通函数 `P()` 输出 `11`
|
||
- `unit interface` 里声明的字面量默认值 `H()` 输出 `5`
|
||
- `F(1)` 输出 `101`,`F(1, 2)` 输出 `3`
|
||
- 同一组文件下,`UnitConst.CS` 当前仍可读到 `888`,而 `F(1, 2, 3)` 输出 `6`
|
||
- 因此当前解释器下,不要把“普通函数默认参数可用”直接泛化成“`unit interface` 里引用 `unit const` 的默认参数也同样可靠”
|
||
- 这组多文件结果依赖真实查找路径;当前验证命令是把对应目录放进 `-LIBPATH`
|
||
- 这类跨 `unit` 的声明边界,统一回看 [10_units_and_scope.md](10_units_and_scope.md)
|
||
|
||
### 可变参数 `...`
|
||
|
||
尾部可变参数:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
function SumAll(...);
|
||
begin
|
||
s := 0;
|
||
for i, v in Params do
|
||
s := s + v;
|
||
return s;
|
||
end;
|
||
```
|
||
|
||
可变参数转发:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
function Forward(...);
|
||
begin
|
||
return SumAll(...);
|
||
end;
|
||
|
||
function SumAll(...);
|
||
begin
|
||
s := 0;
|
||
for i, v in Params do
|
||
s := s + v;
|
||
return s;
|
||
end;
|
||
```
|
||
|
||
`ParamCount` 与 `RealParamCount`:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
function CountArgs(a, b, ...);
|
||
begin
|
||
return ParamCount * 10 + RealParamCount;
|
||
end;
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `SumAll(1, 2, 3, 4)` 返回 `10`
|
||
- `Forward(1, 2, 3, 4)` 返回 `10`
|
||
- `CountArgs(1, 2, 3, 4)` 返回 `44`
|
||
- `CountArgs(1, 2)` 返回 `22`
|
||
|
||
通过 `call` 与 `##` 转发可变参数:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
function Sum3(a, b, c);
|
||
begin
|
||
return a + b + c;
|
||
end;
|
||
function DoFunc(fc, ...);
|
||
begin
|
||
return call(fc, ...);
|
||
end;
|
||
function DoFunc2(fc, ...);
|
||
begin
|
||
return ##fc(...);
|
||
end;
|
||
begin
|
||
WriteLn(DoFunc("Sum3", 1, 2, 3));
|
||
WriteLn(DoFunc2(thisfunction(Sum3), 1, 2, 3));
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `DoFunc("Sum3", 1, 2, 3)` 输出 `6`
|
||
- `DoFunc2(thisfunction(Sum3), 1, 2, 3)` 输出 `6`
|
||
|
||
通过 `invoke` 转发可变参数:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type TestC = class
|
||
public
|
||
function FuncA(a, b, c);
|
||
begin
|
||
return a + b + c;
|
||
end;
|
||
function FuncB(a, b, c);
|
||
begin
|
||
return a * 100 + b * 10 + c;
|
||
end;
|
||
function Dispatch(kind, ...);
|
||
begin
|
||
if kind = "Int" then
|
||
return FuncA(...)
|
||
else
|
||
return FuncB(...);
|
||
end;
|
||
end;
|
||
function DoInvoke(fc_name, ...);
|
||
begin
|
||
obj := new TestC();
|
||
return invoke(obj, fc_name, 0, ...);
|
||
end;
|
||
begin
|
||
WriteLn(DoInvoke("Dispatch", "Int", 2, 20, 200));
|
||
WriteLn(DoInvoke("Dispatch", "Other", 1, 2, 3));
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `DoInvoke("Dispatch", "Int", 2, 20, 200)` 输出 `222`
|
||
- `DoInvoke("Dispatch", "Other", 1, 2, 3)` 输出 `123`
|
||
|
||
### 匿名函数与函数指针
|
||
|
||
匿名函数变量:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
begin
|
||
a := function(x, y)
|
||
begin
|
||
return x + y;
|
||
end;
|
||
WriteLn(call(a, 1, 2));
|
||
WriteLn(##a(5, 6));
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `call(a, 1, 2)` 输出 `3`
|
||
- `##a(5, 6)` 输出 `11`
|
||
|
||
匿名函数也可以直接作为参数传入:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
function Apply(fun);
|
||
begin
|
||
return call(fun, 2, 3);
|
||
end;
|
||
begin
|
||
WriteLn(Apply(function(x, y)
|
||
begin
|
||
return x * y;
|
||
end));
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 输出 `6`
|
||
|
||
函数指针变量也可以通过 `FindFunction(...)` 拿到:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
function Add(a, b);
|
||
begin
|
||
return a + b;
|
||
end;
|
||
begin
|
||
f := FindFunction("Add");
|
||
WriteLn(##f(1, 2));
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 输出 `3`
|
||
|
||
也可以通过 `ThisFunction(...)` 从已知函数名直接拿到:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
function Add(a, b);
|
||
begin
|
||
return a + b;
|
||
end;
|
||
begin
|
||
f := ThisFunction(Add);
|
||
WriteLn(Call(f, 3, 4));
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 输出 `7`
|
||
- 说明 `ThisFunction(Add)` 当前可以得到稳定可调用的函数值
|
||
|
||
直接 `f(...)` 在当前解释器里不要当成可靠写法:
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
// 匿名函数变量直接调用
|
||
program test;
|
||
begin
|
||
a := function(x, y)
|
||
begin
|
||
return x + y;
|
||
end;
|
||
WriteLn(a(7, 8));
|
||
end.
|
||
|
||
// FindFunction 返回值直接调用
|
||
program test;
|
||
function Add(a, b);
|
||
begin
|
||
return a + b;
|
||
end;
|
||
begin
|
||
f := FindFunction("Add");
|
||
WriteLn(f(3, 4));
|
||
end.
|
||
|
||
// ThisFunction 返回值直接调用
|
||
program test;
|
||
function Add(a, b);
|
||
begin
|
||
return a + b;
|
||
end;
|
||
begin
|
||
f := ThisFunction(Add);
|
||
WriteLn(f(3, 4));
|
||
end.
|
||
```
|
||
|
||
已验证结果:
|
||
|
||
- 上面三种 `f(...)` 直调写法都会报 `function:<name> compile error or not found`
|
||
- 因此当前手册只把 `call(f, ...)` 和 `##f(...)` 写成稳定规则
|
||
|
||
### `::` 指向全局函数
|
||
|
||
当当前作用域里有同名局部函数时,可以用 `::FuncName(...)` 指定去调全局/系统函数:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
function Demo();
|
||
begin
|
||
return array(StrToInt("123"), ::StrToInt("123"));
|
||
end;
|
||
function StrToInt(s);
|
||
begin
|
||
return 888;
|
||
end;
|
||
begin
|
||
r := Demo();
|
||
WriteLn(r[0]);
|
||
WriteLn(r[1]);
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 第一项 `StrToInt("123")` 命中局部函数,输出 `888`
|
||
- 第二项 `::StrToInt("123")` 命中全局系统函数,输出 `123`
|
||
- 说明 `::` 可以绕过当前作用域的同名局部函数,直接指向全局/系统函数
|
||
|
||
如果 `::FuncName(...)` 指向的全局函数本身不存在,当前解释器会在运行时报 `function:<name> compile error or not found`
|
||
|
||
### 系统交互专题
|
||
|
||
`external`、动态库函数指针、`MakeInstance` 和线程调用,统一见 [21_external_calls_and_threads.md](21_external_calls_and_threads.md)。这一篇只保留“普通函数怎样定义和调用”的主线。
|
||
|
||
## 最小可编译示例
|
||
|
||
如果你只是要写一个能被 session 稳定续写的函数 / 过程,从下面任一骨架起步:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
function Hello();
|
||
begin
|
||
return 1;
|
||
end;
|
||
```
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
procedure HelloProc();
|
||
begin
|
||
end;
|
||
```
|
||
|
||
## 常见误写
|
||
|
||
- 把顶层函数定义和松散语句混写。
|
||
- 以为函数头后的分号是当前解释器的硬性要求。
|
||
- 以为 `procedure` 只是 `function` 的别名,不涉及参数传递语义。
|
||
- 带类型注解时仍然用逗号分隔参数。
|
||
- 以为默认未修饰参数天然就是按值传递。
|
||
- 把 `a = 1` 这种比较表达式误当成命名参数调用。
|
||
- 以为默认值只能用于无类型参数。
|
||
- 在 `const` 形参上直接赋值。
|
||
- 把普通函数的默认值规则原样套到 `unit interface` 里的 `const` 默认参数上。
|
||
- 把匿名函数或 `FindFunction(...)` 返回值默认写成 `f(...)` 直调。
|
||
- 把命名参数直接套到二进制函数或系统函数上。
|
||
- 在一次调用里先进入命名参数模式,后面又退回位置参数。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
function Add(a, b);
|
||
begin
|
||
return a + b;
|
||
end;
|
||
|
||
value := Add(1, 2);
|
||
```
|
||
|
||
上面这种混写方式会编译失败,问题不在 `Add` 本身,而在于文件模型混了“函数定义体”和“松散语句”两种写法。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
function Demo(a: integer, b: integer);
|
||
begin
|
||
return a + b;
|
||
end;
|
||
```
|
||
|
||
上面这种写法会编译失败;参数一旦带类型,分隔符应改成分号。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
Pack(a = 1, b = 2)
|
||
```
|
||
|
||
这类写法不要当成命名参数。它虽然可能编译通过,但在我于 `2026-04-09` 的实测里返回结果不对,不能当成可靠的命名参数语法。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
function Demo(a: integer, b: integer = 2): integer;
|
||
begin
|
||
return a + b;
|
||
end;
|
||
```
|
||
|
||
上面这种写法也不对;参数一旦带类型,分隔符仍然应保持分号。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
function TouchDefault(a);
|
||
begin
|
||
a := 9;
|
||
end;
|
||
```
|
||
|
||
不要把上面这种未修饰参数自动理解成“按值传递”。在当前解释器里,它会把调用方实参改掉;如果当前任务要求按值语义,先看 `{$VarByRef-}` 和 `in` / `out` 的例子。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
procedure Bad(const x);
|
||
begin
|
||
x := 2;
|
||
end;
|
||
```
|
||
|
||
上面这种写法会编译失败;`const` 形参在当前解释器里不能被重新赋值。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
unit UnitConst;
|
||
interface
|
||
const CS = 888;
|
||
function F(a, b = 100, c = CS);
|
||
```
|
||
|
||
不要把上面这种 `unit interface` 声明直接当成已经等价于普通函数默认参数规则。在当前解释器里,对应的 `F(1)` 实测输出是 `101`,不是按 `CS = 888` 补成的结果;具体边界见 [10_units_and_scope.md](10_units_and_scope.md)。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
a := function(x, y)
|
||
begin
|
||
return x + y;
|
||
end;
|
||
WriteLn(a(7, 8));
|
||
```
|
||
|
||
不要把上面这种匿名函数变量的直调写法直接当成可靠规则。在当前解释器里,它会报 `function:a compile error or not found`;稳定写法仍然是 `call(a, 7, 8)` 或 `##a(7, 8)`。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
WriteLn(IntToStr(value: 200));
|
||
```
|
||
|
||
不要把上面这种写法直接套到二进制函数或系统函数上。当前解释器里,它会报 `named parameter mode can't support here`;如果确实要命名传参,先用 TSL 函数再封一层。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
function Pack(a, b, c);
|
||
begin
|
||
return a * 100 + b * 10 + c;
|
||
end;
|
||
begin
|
||
WriteLn(Pack(a: 1, 2, c: 3));
|
||
end.
|
||
```
|
||
|
||
不要在同一次调用里“先进入命名参数模式,再退回位置参数”。当前这类写法会直接编译失败,错误信息包含 `paramname: not found`。
|
||
|
||
## 跳转指引
|
||
|
||
- 回看最短骨架:见 [02_quickstart.md](02_quickstart.md)
|
||
- 回看流程控制:见 [08_control_flow.md](08_control_flow.md)
|
||
- 当函数变多时:见 [10_units_and_scope.md](10_units_and_scope.md)
|
||
- 看 `external`、`MakeInstance` 和线程调用:见 [21_external_calls_and_threads.md](21_external_calls_and_threads.md)
|