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

410 lines
11 KiB
Markdown
Raw Permalink 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.

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