# 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` 的同名接口。