410 lines
11 KiB
Markdown
410 lines
11 KiB
Markdown
# Units And Scope
|
||
|
||
文档类型:语法主线
|
||
是否可直接用于生成代码:仅部分
|
||
是否含已验证可执行示例:是
|
||
是否含已验证反例:是
|
||
遇到不确定时跳转到:[22_namespace_libpath_and_unit_runtime.md](22_namespace_libpath_and_unit_runtime.md)(优先)、[03_core_model.md](03_core_model.md)、[12_pitfalls.md](12_pitfalls.md)
|
||
|
||
手册位置:第 10 篇,共 32 篇。上一篇:[09_objects_and_classes.md](09_objects_and_classes.md)。下一篇:[11_runtime_context_and_with.md](11_runtime_context_and_with.md)。
|
||
|
||
这一篇只讲“怎么写和怎么调用 `unit`”。`namespace`、查找路径、`unit` 生命周期、`tsl.conf` 与运行时入口统一放到 [22_namespace_libpath_and_unit_runtime.md](22_namespace_libpath_and_unit_runtime.md)。
|
||
|
||
## 这一篇解决什么问题
|
||
|
||
回答下面几类最常见的问题:
|
||
|
||
- 什么时候该把一组函数写成 `unit`
|
||
- `uses` 到底能放在哪里
|
||
- `interface uses` 和 `implementation uses` 的可见性差别是什么
|
||
- 多个 `unit` 里有同名接口时,未限定调用到底命中谁
|
||
- 怎样显式指定要调用哪个 `unit` 的接口
|
||
|
||
## 必须记住的规则
|
||
|
||
- `unit` 是完整的顶层主体;常见完整形态是 `unit Name; interface ... implementation ... end.`。
|
||
- `unit` 也可以省略 `interface` / `implementation` 写成简写形态;当前解释器下,这种简写里定义的函数对外可调用。
|
||
- 如果没有特殊需求,默认优先用完整形态;简写形态只在不需要显式区分 `interface` / `implementation` 时再用。
|
||
- 顶层 `uses` 可以放在文件最前面,让后续整个文件直接看到被引入 `unit` 的接口。
|
||
- 当前解释器允许连续多条顶层 `uses`;真正会失败的是前面已经出现普通语句后,再写顶层 `uses`。
|
||
- 函数体里的 `uses` 必须是函数体第一条语句,而且一个函数体里只能写一次。
|
||
- 类定义体里的 `uses` 必须是类定义体第一条语句,而且一个类定义体里只能写一次。
|
||
- `interface uses` 对整个 `unit` 可见;`implementation uses` 只对实现段和在实现段里定义的方法体可见。
|
||
- 只写在 `implementation` 里的函数,对 `unit` 外部不可见。
|
||
- 出现重名接口时,未限定调用按 `uses` 从后往前解析;最后一个 `uses` 的同名接口优先。
|
||
- 要显式指定某个 `unit` 的接口,可以用 `UnitA.Ping()`、`unit(UnitA).Ping()` 或 `call("UnitA.Ping")`。
|
||
- `unit` 生命周期、接口状态、命名空间和查找路径细节,转去看 [22_namespace_libpath_and_unit_runtime.md](22_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`
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```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` 位置:
|
||
|
||
代码块身份:配置片段 / 概念骨架
|
||
代码块说明:已在多文件环境下验证;这里只用来展示结构和结果,不是可直接复制的单文件最小示例。
|
||
|
||
```text
|
||
// DemoUnit.tsf
|
||
unit DemoUnit;
|
||
interface
|
||
function Ping();
|
||
implementation
|
||
function Ping();
|
||
begin
|
||
return 11;
|
||
end;
|
||
end.
|
||
|
||
// main.tsl
|
||
program test;
|
||
uses DemoUnit;
|
||
function RunInFunction();
|
||
begin
|
||
uses DemoUnit;
|
||
return Ping();
|
||
end;
|
||
type Worker = class
|
||
uses DemoUnit;
|
||
function Run();
|
||
begin
|
||
return Ping();
|
||
end;
|
||
end;
|
||
begin
|
||
obj := new Worker();
|
||
WriteLn(Ping());
|
||
WriteLn(RunInFunction());
|
||
WriteLn(obj.Run());
|
||
end.
|
||
|
||
// command
|
||
tsl .\main.tsl -LIBPATH "D:\path\to\dir\"
|
||
```
|
||
|
||
已验证结果:
|
||
|
||
- 顶层 `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
|
||
value;
|
||
uses DemoUnit;
|
||
end;
|
||
|
||
// 类里写两次
|
||
type Worker = class
|
||
uses UnitA;
|
||
uses UnitB;
|
||
end;
|
||
```
|
||
|
||
已验证结果:
|
||
|
||
- 顶层前面如果已经出现 `a := 1;` 这类普通语句,再写顶层 `uses` 会报 `invalid statement`。
|
||
- 函数体里,`uses` 不是第一行或者重复出现,都会失败。
|
||
- 类定义体里,`uses` 不是第一行或者重复出现,都会失败。
|
||
- 当前解释器允许连续多条顶层 `uses`,所以“顶层只能写一条 `uses`”不是已验证事实。
|
||
|
||
### `interface uses` 和 `implementation uses`
|
||
|
||
代码块身份:配置片段 / 概念骨架
|
||
代码块说明:已在多文件环境下验证;这里只用来展示结构和结果,不是可直接复制的单文件最小示例。
|
||
|
||
```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
|
||
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
|
||
program test;
|
||
uses UnitA;
|
||
begin
|
||
obj := new TBox();
|
||
WriteLn(CallB());
|
||
WriteLn(CallC());
|
||
WriteLn(obj.FromInterface());
|
||
WriteLn(obj.FromImplementation());
|
||
end.
|
||
```
|
||
|
||
已验证结果:
|
||
|
||
- `interface uses UnitB;` 后,`FB()` 对整个 `unit` 都可见,`CallB()` 输出 `100`。
|
||
- `implementation uses UnitC;` 后,`FC()` 对实现段函数和在实现段定义的方法体可见,`CallC()` 与 `obj.FromImplementation()` 都输出 `200`。
|
||
- 接口段里的内联方法 `FromInterface()` 能访问 `interface uses` 引入的接口,因此输出 `100`。
|
||
- 当前解释器没有让接口段里的内联方法看到只在 `implementation uses` 引入的接口。
|
||
|
||
### 实现段私有函数
|
||
|
||
代码块身份:配置片段 / 概念骨架
|
||
代码块说明:已在多文件环境下验证;这里只用来展示结构和结果,不是可直接复制的单文件最小示例。
|
||
|
||
```text
|
||
// PrivateDemo.tsf
|
||
unit PrivateDemo;
|
||
interface
|
||
function PublicFunc();
|
||
implementation
|
||
function PublicFunc();
|
||
begin
|
||
return PrivateFunc();
|
||
end;
|
||
function PrivateFunc();
|
||
begin
|
||
return 77;
|
||
end;
|
||
end.
|
||
|
||
// main.tsl
|
||
program test;
|
||
uses PrivateDemo;
|
||
begin
|
||
WriteLn(PublicFunc());
|
||
WriteLn(PrivateFunc());
|
||
end.
|
||
```
|
||
|
||
已验证结果:
|
||
|
||
- `PublicFunc()` 输出 `77`,说明接口函数可以调用实现段私有函数。
|
||
- 外部直接调用 `PrivateFunc()` 时,会报 `function:PrivateFunc compile error or not found`。
|
||
|
||
### 重名解析与限定调用
|
||
|
||
重名函数最小对照:
|
||
|
||
代码块身份:配置片段 / 概念骨架
|
||
代码块说明:已在多文件环境下验证;这里只用来展示结构和结果,不是可直接复制的单文件最小示例。
|
||
|
||
```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`。
|
||
|
||
重名类最小对照:
|
||
|
||
代码块身份:配置片段 / 概念骨架
|
||
代码块说明:已在多文件环境下验证;这里只用来展示结构和结果,不是可直接复制的单文件最小示例。
|
||
|
||
```text
|
||
// UnitA.tsf
|
||
unit UnitA;
|
||
interface
|
||
type Box = class
|
||
value;
|
||
function Create();
|
||
begin
|
||
value := 101;
|
||
end;
|
||
end;
|
||
implementation
|
||
end.
|
||
|
||
// UnitB.tsf
|
||
// 与 UnitA.tsf 同形,只把 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` 只能写一次”当成当前解释器规则。
|
||
- 把重名接口的未限定解析顺序理解成“从前到后”。
|
||
- 忘了显式指定 `UnitA.Ping()`、`unit(UnitA).Ping()` 或 `call("UnitA.Ping")`,结果误调用到最后一个 `uses` 的同名接口。
|
||
|
||
## 跳转指引
|
||
|
||
- 回看文件与顶层主体:见 [03_core_model.md](03_core_model.md)
|
||
- 查看 `unit` 生命周期、命名空间与查找路径:见 [22_namespace_libpath_and_unit_runtime.md](22_namespace_libpath_and_unit_runtime.md)
|
||
- 继续看对象运行时:见 [23_object_runtime_and_introspection.md](23_object_runtime_and_introspection.md)
|