playbook/docs/tsl/syntax/09_units_and_scope.md

510 lines
13 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 unit 与作用域
文档类型:语法主线
是否可直接用于生成代码:仅部分
是否含可直接照写示例:是
是否含不可照写反例:是
遇到不确定时:先按本页候选页继续判断;[02_core_model.md](02_core_model.md)(优先)、[19_namespace_libpath_and_unit_runtime.md](19_namespace_libpath_and_unit_runtime.md)、[11_pitfalls.md](11_pitfalls.md);仍不命中时回到语法路由中心 [index.md](index.md);如果问题已经超出语法层,回到 TSL 总入口 [../index.md](../index.md)
这一篇只讲“怎么写和怎么调用 `unit`”。`namespace`、查找路径、`unit` 生命周期、`tsl.conf` 与运行时入口统一放到 [19_namespace_libpath_and_unit_runtime.md](19_namespace_libpath_and_unit_runtime.md)。
## 本篇职责
回答下面几类最常见的问题:
- 什么时候该把一组函数写成 `unit`
- `uses` 到底能放在哪里
- `interface``uses``implementation``uses` 的可见性差别是什么
- 多个 `unit` 里有同名接口时,未限定调用到底命中谁
- 怎样显式指定要调用哪个 `unit` 的接口
## 智能体 unit/作用域判断流程
1. 先判断当前交付是 `.tsl` 可执行脚本、`.tsf` 扩展模块,还是 `unit` 文件。
2. `unit` 文件只描述可复用单元;脚本入口仍放在 `.tsl`
3. `uses` 必须放在普通语句之前;普通语句后不要再追加顶层 `uses`
4. 默认参数、接口段、实现段和作用域边界只照本页文档明确形态写。
5. 命中 `unit` 生命周期、命名空间、查找路径、`tsl.conf` 或命令行查找路径参数时,跳到 [19_namespace_libpath_and_unit_runtime.md](19_namespace_libpath_and_unit_runtime.md)。
6. 没有对应代码块时不要发明 unit/作用域写法。
## 核心规则
- `unit` 是完整的顶层主体;常见完整形态是 `unit Name; interface ... implementation ... end.`
- `unit` 也可以省略 `interface` / `implementation` 写成简写形态;这种简写里定义的函数对外可调用。
- 如果没有特殊需求,默认优先用完整形态;简写形态只在不需要显式区分 `interface` / `implementation` 时再用。
- 完整 `unit` 示例中,`implementation` 前保留空行,避免接口段和实现段挤在一起。
- `unit` 允许接口声明与实现段分离:`interface` 段可以只声明函数签名或类方法签名,函数体和类方法体放到 `implementation` 段。
- 类方法放在 `implementation` 段实现时,使用 `function ClassName.Method(...)` 形态。
- 顶层 `uses` 可以放在文件最前面,让后续整个文件直接看到被引入 `unit` 的接口。
- 顶层导入多个 `unit` 时,只生成单条逗号合并写法:`uses UnitA, UnitB;`;不要生成连续多条顶层 `uses`。真正会失败的是前面已经出现普通语句后,再写顶层 `uses`
- 函数体里的 `uses` 必须是函数体第一条语句,而且一个函数体里只能写一次。
- 类定义体里的 `uses` 必须是类定义体第一条语句,而且一个类定义体里只能写一次。
- 如果函数体或类定义体需要多个 `unit`,默认把这些 `uses` 提升到顶层并合并成单条 `uses UnitA, UnitB;`;不要在函数体或类定义体里连写两条 `uses`
- 写在 `interface` 段的 `uses` 对整个 `unit` 可见;写在 `implementation` 段的 `uses` 只对实现段和在实现段里定义的方法体可见。
- 只写在 `implementation` 里的函数,对 `unit` 外部不可见。
- 出现重名接口时,未限定调用按 `uses` 从后往前解析;最后一个 `uses` 的同名接口优先。
- 要显式指定某个 `unit` 的接口,可以用 `UnitA.Ping()`、`unit(UnitA).Ping()` 或 `call("UnitA.Ping")`
- `unit` 生命周期、接口状态、命名空间和查找路径细节,转去看 [19_namespace_libpath_and_unit_runtime.md](19_namespace_libpath_and_unit_runtime.md)。
## 可直接照写示例
### 最小 `unit`
代码块身份:可直接照写示例
```tsl
unit DemoUnit;
interface
function Ping();
implementation
function Ping();
begin
return 1;
end;
end.
```
结果说明:
- 上面这就是最小完整 `unit` 骨架。
- `unit` 文件结尾要用 `end.`,不是普通函数或类的 `end;`
如果该 `unit` 被脚本 `uses DemoUnit;` 后调用 `writeLn(Ping());`,可观察输出为:
代码块身份:输出片段
```text
1
```
### 简写 `unit`
代码块身份:可直接照写示例
```tsl
unit SpecialUnit;
function Abcd();
begin
return 10;
end;
function Abcd2();
begin
return 20;
end;
initialization
writeLn("INIT");
finalization
writeLn("FINAL");
end.
```
结果说明:
- `unit SpecialUnit;` 后可以省略 `interface` / `implementation`
- 这种简写形态里定义的 `Abcd()`、`Abcd2()` 外部可以直接调用。
- 简写 `unit` 里同样可以写 `initialization``finalization`
### 顶层、函数体、类体里的 `uses`
沿用同一个 `DemoUnit.tsf`,分别看顶层、函数体、类体三种 `uses` 位置:
代码块身份:配置片段 / 概念骨架
代码块说明:多文件结构骨架;依赖 `unit` 查找路径,不是可直接复制的单文件最小示例。
```text
// DemoUnit.tsf
unit DemoUnit;
interface
function Ping();
implementation
function Ping();
begin
return 11;
end;
end.
// main.tsl
uses DemoUnit;
obj := new Worker();
writeLn(Ping());
writeLn(RunInFunction());
writeLn(obj.Run());
function RunInFunction();
begin
uses DemoUnit;
return Ping();
end;
type Worker = class
uses DemoUnit;
public
function Run();
begin
return Ping();
end;
end;
```
结果说明:
- 顶层 `uses DemoUnit;` 后,脚本主体可以直接调用 `Ping()`
- 函数体第一行写 `uses DemoUnit;` 后,`RunInFunction()` 输出 `11`
- 类定义体第一行写 `uses DemoUnit;` 后,`obj.Run()` 也输出 `11`
- 这一类多文件场景依赖真实查找路径;如果 `DemoUnit.tsf` 所在目录不在查找路径里,脚本仍会找不到对应接口。
### `uses` 的位置限制
代码块身份:反例 / 不可照写
```text
// 顶层前面已经有普通语句
a := 1;
uses DemoUnit;
// 函数里不是第一行
function Run();
begin
a := 1;
uses DemoUnit;
return Ping();
end;
// 函数里写两次
function Run();
begin
uses UnitA;
uses UnitB;
return Ping();
end;
// 类里不是第一行
type Worker = class
public
value;
uses DemoUnit;
end;
// 类里写两次
type Worker = class
uses UnitA;
uses UnitB;
end;
```
结果说明:
- 顶层前面如果已经出现 `a := 1;` 这类普通语句,再写顶层 `uses` 会报 `invalid statement`
- 函数体里,`uses` 不是第一行或者重复出现,都会失败。
- 类定义体里,`uses` 不是第一行或者重复出现,都会失败。
- 函数体或类定义体里重复 `uses` 的失败点是“第二条 `uses`”,不是顶层导入多个 `unit`
- 顶层导入多个 `unit` 时只生成 `uses UnitA, UnitB;` 这种逗号合并写法;连续多条顶层 `uses` 不再作为文档明确生成形态。
### `interface` 段 `uses` 和 `implementation` 段 `uses`
代码块身份:配置片段 / 概念骨架
代码块说明:多文件结构骨架;依赖 `unit` 查找路径,不是可直接复制的单文件最小示例。
```text
// UnitB.tsf
unit UnitB;
interface
function FB();
implementation
function FB();
begin
return 100;
end;
end.
// UnitC.tsf
// 与 UnitB.tsf 同形,只把 FB / 100 改成 FC / 200
// UnitA.tsf
unit UnitA;
interface
uses UnitB;
type TBox = class
public
function FromInterface();
begin
return FB();
end;
function FromImplementation();
end;
function CallB();
function CallC();
implementation
uses UnitC;
function TBox.FromImplementation();
begin
return FC();
end;
function CallB();
begin
return FB();
end;
function CallC();
begin
return FC();
end;
end.
// main.tsl
uses UnitA;
obj := new TBox();
writeLn(CallB());
writeLn(CallC());
writeLn(obj.FromInterface());
writeLn(obj.FromImplementation());
```
结果说明:
-`interface` 段写 `uses UnitB;` 后,`FB()` 对整个 `unit` 都可见,`CallB()` 输出 `100`
-`implementation` 段写 `uses UnitC;` 后,`FC()` 对实现段函数和在实现段定义的方法体可见,`CallC()` 与 `obj.FromImplementation()` 都输出 `200`
- 接口段里的内联方法 `FromInterface()` 能访问 `interface``uses` 引入的接口,因此输出 `100`
- 接口段里的内联方法不能看到只在 `implementation``uses` 引入的接口。
### 接口声明与实现段分离
代码块身份:可直接照写示例
```tsl
unit SplitDemo;
interface
function MakeValue();
type UnitBox = class
public
value;
function create(_value);
function ReadValue();
end;
implementation
function MakeValue();
begin
return 10;
end;
function UnitBox.create(_value);
begin
value := _value;
end;
function UnitBox.ReadValue();
begin
return value;
end;
end.
```
结果说明:
- 普通函数可以在 `interface` 段只声明 `function MakeValue();`,再在 `implementation` 段写函数体。
- 类方法可以在类体里只声明签名,再在 `implementation` 段写 `function UnitBox.Method(...)` 实现。
- 类方法实现放在 `implementation` 段时,仍属于该 `unit` 的实现段。
### 实现段私有函数
代码块身份:配置片段 / 概念骨架
代码块说明:多文件结构骨架;依赖 `unit` 查找路径,不是可直接复制的单文件最小示例。
```text
// PrivateDemo.tsf
unit PrivateDemo;
interface
function PublicFunc();
implementation
function PublicFunc();
begin
return PrivateFunc();
end;
function PrivateFunc();
begin
return 77;
end;
end.
// main.tsl
uses PrivateDemo;
writeLn(PublicFunc());
```
结果说明:
- `PublicFunc()` 输出 `77`,说明接口函数可以调用实现段私有函数。
- 外部直接调用 `PrivateFunc()` 时,会报 `function:PrivateFunc compile error or not found`
代码块身份:反例 / 不可照写
```text
uses PrivateDemo;
writeLn(PrivateFunc());
```
上面这种写法不可照写;`PrivateFunc()` 只写在 `implementation` 段,对 `unit` 外部不可见。
### 重名解析与限定调用
重名函数最小对照:
代码块身份:配置片段 / 概念骨架
代码块说明:多文件结构骨架;依赖 `unit` 查找路径,不是可直接复制的单文件最小示例。
```text
// UnitA.tsf
unit UnitA;
interface
function Ping();
function Hello();
implementation
function Ping();
begin
return 101;
end;
function Hello();
begin
return 111;
end;
end.
// UnitB.tsf
// 与 UnitA.tsf 同形,只把 101 / 111 改成 202 / 222
// main.tsl
uses UnitA, UnitB;
writeLn(Ping());
writeLn(UnitA.Hello());
writeLn(UnitB.Hello());
writeLn(unit(UnitA).Ping());
writeLn(call("UnitB.Ping"));
```
结果说明:
- 未限定的 `Ping()` 输出 `202`,说明解析顺序是 `uses` 从后往前。
- `UnitA.Hello()` 输出 `111``UnitB.Hello()` 输出 `222`
- `unit(UnitA).Ping()` 输出 `101`
- `call("UnitB.Ping")` 输出 `202`
重名类最小对照:
代码块身份:配置片段 / 概念骨架
代码块说明:多文件结构骨架;依赖 `unit` 查找路径,不是可直接复制的单文件最小示例。
```text
// UnitA.tsf
unit UnitA;
interface
type Box = class
public
value;
function create();
end;
implementation
function Box.create();
begin
value := 101;
end;
end.
// UnitB.tsf
// 与 UnitA.tsf 同形,只把 Box.create() 里写入的 101 改成 202
// main.tsl
uses UnitA, UnitB;
obj := new Box();
writeLn(obj.value);
objA := createObject("UnitA.Box");
writeLn(objA.value);
objB := createObject("UnitB.Box");
writeLn(objB.value);
```
结果说明:
- 未限定的 `new Box()` 输出 `202`
- `createObject("UnitA.Box")` 输出 `101`
- `createObject("UnitB.Box")` 输出 `202`
- 重名类的未限定解析顺序和重名函数一致,也是按 `uses` 从后往前。
## 默认生成模板
如果你只是要先写出一个最小、可被其他文件引用的 `unit`,从下面这个骨架起步:
代码块身份:可直接照写示例
```tsl
unit DemoUnit;
interface
function Ping();
implementation
function Ping();
begin
return 1;
end;
end.
```
后面的多文件片段只在需要 `uses`、可见性或重名解析时再回来看。
## 禁止项
- 把依赖外部 `unit``uses` 例子误当成单文件最小示例。
- 在还没把目标 `unit` 放进查找路径前,就把“找不到接口”误判成 `uses` 语法错。
- 把函数体或类定义体里的 `uses` 写在第一条语句之后。
- 在同一个函数体或类定义体里重复写第二个 `uses`
- 生成连续多条顶层 `uses`;多个 `unit` 默认写成 `uses UnitA, UnitB;`
- 把重名接口的未限定解析顺序理解成“从前到后”。
- 忘了显式指定 `UnitA.Ping()`、`unit(UnitA).Ping()` 或 `call("UnitA.Ping")`,结果误调用到最后一个 `uses` 的同名接口。