# TSL 命名空间、Libpath 与 unit 运行时 文档类型:语法深水专题 是否可直接用于生成代码:仅部分 是否含可直接照写示例:是 是否含不可照写反例:是 遇到不确定时:先按本页候选页继续判断;[09_units_and_scope.md](09_units_and_scope.md)、[20_object_runtime_and_introspection.md](20_object_runtime_and_introspection.md)、[11_pitfalls.md](11_pitfalls.md);仍不命中时回到语法路由中心 [index.md](index.md);如果问题已经超出语法层,回到 TSL 总入口 [../index.md](../index.md) 这一篇收拢 `unit` 的运行时行为,以及 `.tsf` / `namespace` / `libpath` 的查找规则。`unit` 的基础写法、`uses` 位置和重名解析,统一放在 [09_units_and_scope.md](09_units_and_scope.md)。 ## 本篇职责 回答下面这些“写法已经会了,但运行起来为什么这样”的问题: - `unit` 什么时候进入 `initialization` - `finalization` 在什么时候触发 - `unit` 里的常量、变量和 `findFunction("UnitName")` 到底暴露哪些成员 - `tslfilename()` 返回什么 - `namespace "..."`、`tsl.conf` 和 `-LIBPATH` 怎样影响 `.tsf` 查找 - `syssettsllibpath()` / `sysgettsllibpath()` 怎样在运行时改查找路径 ## 智能体命名空间/Libpath 判断流程 1. 先判断任务是单元命名空间访问、`-LIBPATH` 查找,还是配置文件查找。 2. 路径和查找顺序只写平台中立规则,不写某个开发机或容器路径。 3. `-LIBPATH` 只作为运行时部署/查找规则,不写进普通语法示例。 4. `Libpath={$P}...` 和 `InheritParent=1` 不作为本页文档能力。 5. 没有对应代码块时不要发明命名空间/Libpath 写法。 ## 核心规则 - 完整 `unit` 形态可以包含 `interface`、`implementation`、`initialization`、`finalization`,并以 `end.` 结束。 - `initialization` 在 `unit` 第一次被实际使用时触发,不是只因为顶层写了 `uses` 就立刻执行。 - `finalization` 会在脚本结束前触发。 - 直接写 `DemoUnit.Member` 时,可以读到 `interface` 和 `implementation` 里的常量、变量。 - `findFunction("DemoUnit")` 拿到的是 `unit` 对象入口;本页只把它稳定暴露 `interface` 成员写成文档事实。 - `DemoUnit.var_name := value` 这种限定赋值不作为可写事实;如果要改 `unit` 状态,应导出函数或方法来改。 - `tslfilename()` 的参数规格见 [../reference/catalog/system.md](../reference/catalog/system.md);本页只保留它返回正在执行的 `.tsl` 主脚本完整路径这一行为事实。 - `namespace "DemoNS";` 会选择 `Hello@DemoNS.tsf` 这类命名空间函数文件。 - `tsl.conf` 的 `[system] Namespace=...` 可以设置默认命名空间;脚本里的 `namespace "..."` 会覆盖配置值。 - 当全局 `Hello.tsf` 和 `Hello@StmtNS.tsf` 同时存在时,启用 `StmtNS` 后会优先命中命名空间版本。 - `-LIBPATH` 必须跟在脚本文件名后面;目录项必须以 `/` 或 `\` 结尾;多路径分隔符是分号 `;`。 - 当多个 `-LIBPATH` 目录都存在同名 `.tsf` 时,解释器按路径从前到后查找,先命中的目录优先。 - `tsl.conf` 里的绝对 `Libpath=` 可以生效;`Libpath={$P}...` 不作为本页文档能力。 - `syssettsllibpath()` / `sysgettsllibpath()` 可以在运行时修改和读取查找路径,路径规则与 `-LIBPATH` 相同。 - `InheritParent=1` 不作为本页文档能力。 ## 示例与行为 ### `unit` 生命周期 代码块身份:配置片段 / 概念骨架 ```text // DemoUnit.tsf unit DemoUnit; interface const unit_value = 7; var unit_counter; function Ping(); implementation var private_counter; function Ping(); begin private_counter := private_counter + 1; unit_counter := unit_counter + 10; return private_counter; end; initialization writeLn("INIT"); unit_counter := 1; private_counter := 100; finalization writeLn("FINAL"); end. // main.tsl uses DemoUnit; writeLn("BEFORE"); writeLn(DemoUnit.unit_value); writeLn(DemoUnit.unit_counter); writeLn(Ping()); writeLn(DemoUnit.unit_counter); ``` 结果说明: - 输出顺序是 `BEFORE`、`INIT`、`7`、`1`、`101`、`11`、`FINAL`。 - 这说明顶层写了 `uses DemoUnit;` 之后,`initialization` 不是立刻执行,而是在第一次真正读 `DemoUnit` 成员时触发。 - `finalization` 在脚本主体输出结束后触发。 代码块身份:输出片段 ```text BEFORE INIT 7 1 101 11 FINAL ``` ### `unit` 成员的读取边界 直接限定读取: 代码块身份:配置片段 / 概念骨架 ```text // DemoUnit.tsf unit DemoUnit; interface const public_const = 1; var public_var; function PublicFunc(); implementation const impl_const = 2; var impl_var; function PublicFunc(); begin return 10; end; function ImplFunc(); begin return 20; end; initialization public_var := 3; impl_var := 4; end. // main.tsl uses DemoUnit; writeLn(DemoUnit.public_const); writeLn(DemoUnit.public_var); writeLn(DemoUnit.impl_const); writeLn(DemoUnit.impl_var); writeLn(PublicFunc()); ``` 结果说明: - `DemoUnit.public_const` 输出 `1`。 - `DemoUnit.public_var` 输出 `3`。 - `DemoUnit.impl_const` 输出 `2`。 - `DemoUnit.impl_var` 输出 `4`。 - `PublicFunc()` 输出 `10`。 - 本页文档边界是:实现段函数仍私有,但实现段常量和变量可以通过 `DemoUnit.Member` 直接读取。 实现段函数的外部调用反例: 代码块身份:反例 / 不可照写 ```text uses DemoUnit; writeLn(ImplFunc()); ``` 结果说明: - 上面这种写法会报 `function:ImplFunc compile error or not found`。 - 因此不要把“实现段常量和变量可限定读取”泛化成“实现段函数可外部调用”。 `findFunction("UnitName")` 拿到的 `unit` 对象: 代码块身份:配置片段 / 概念骨架 ```text // DemoUnit.tsf unit DemoUnit; interface const public_const = 1; var public_var; function Ping(); implementation const impl_const = 2; var impl_var; function Ping(); begin return 11; end; initialization public_var := 3; impl_var := 4; end. // main.tsl uses DemoUnit; u := findFunction("DemoUnit"); writeLn(u.public_const); writeLn(u.public_var); writeLn(u.Ping()); ``` 结果说明: - `u.public_const` 输出 `1`。 - `u.public_var` 输出 `3`。 - `u.Ping()` 输出 `11`。 - `findFunction("DemoUnit")` 拿到的对象入口只稳定暴露 `interface` 成员。 `findFunction("UnitName")` 对象入口访问实现段成员反例: 代码块身份:反例 / 不可照写 ```text uses DemoUnit; u := findFunction("DemoUnit"); writeLn(u.impl_const); ``` 结果说明: - 上面这种写法会报对象属性或方法不存在。 - 因此不要把 `findFunction("DemoUnit")` 的对象入口等同于 `DemoUnit.Member` 限定访问。 限定赋值失败: 代码块身份:反例 / 不可照写 ```text uses DemoUnit; DemoUnit.unit_counter := 13; ``` 结果说明: - 上面这段会编译失败,报错核心是 `left side can not be assign to`。 ### `tslfilename()` 代码块身份:可直接照写示例 ```tsl writeLn(tslfilename()); ``` 结果说明: - 这一行会输出正在执行的 `.tsl` 主脚本完整路径。 - 本页只把“主脚本路径”写成文档事实;不要把 `.tsf` 被调用场景的返回值提前写成事实。 ### `namespace` 与默认命名空间 把默认命名空间、脚本覆盖、以及“全局函数与命名空间函数同名”的三组结论压成同一套最小实验: 代码块身份:配置片段 / 概念骨架 ```text // tsl.conf [system] Namespace=ConfNS // 同一目录下准备三份函数文件 // Hello.tsf function Hello(); begin return 10; end; // Hello@ConfNS.tsf function Hello(); begin return 20; end; // Hello@StmtNS.tsf function Hello(); begin return 30; end; // main_conf.tsl writeLn(Hello()); // main_stmt.tsl namespace "StmtNS"; writeLn(Hello()); ``` 结果说明: - `main_conf.tsl` 输出 `20`,说明 `tsl.conf` 里的 `Namespace=ConfNS` 会默认命中 `Hello@ConfNS.tsf`。 - `main_stmt.tsl` 输出 `30`,说明脚本里的 `namespace "StmtNS";` 会覆盖 `tsl.conf` 里的 `Namespace=ConfNS`。 - 在同一目录保留 `Hello.tsf` 后,不写 `namespace` 且不设置 `Namespace` 时,`Hello()` 输出 `10`。 - 如果只保留 `Hello@StmtNS.tsf` 而没有全局 `Hello.tsf`,又没有启用命名空间,`Hello()` 会报找不到函数。 ### `-LIBPATH` 查找顺序: 代码块身份:配置片段 / 概念骨架 代码块说明:命令行运行形态和多文件结构骨架;这些命令不可写进 `.tsl` 源码。 ```text // libA/Hello.tsf function Hello(); begin return 101; end; // libB/Hello.tsf function Hello(); begin return 202; end; // main.tsl writeLn(Hello()); // 命令 A tsl .\main.tsl -LIBPATH "D:\libA\;D:\libB\" // 命令 B tsl .\main.tsl -LIBPATH "D:\libB\;D:\libA\" ``` 结果说明: - 命令 A 输出 `101`。 - 命令 B 输出 `202`。 - 解释器按 `-LIBPATH` 从前到后查找,先命中的目录优先。 位置与分隔符边界: 代码块身份:配置片段 / 概念骨架 代码块说明:命令行运行形态;这些命令不可写进 `.tsl` 源码。 ```text tsl -LIBPATH "D:\libA\" .\main.tsl tsl .\main.tsl -LIBPATH "D:\libA" tsl .\main.tsl -LIBPATH "D:\libA\,D:\libB\" ``` 结果说明: - 把 `-LIBPATH` 放在脚本文件名前面时,解释器不会执行脚本,而是回到 usage 提示。 - 路径项不带结尾 `/` 或 `\` 时,函数找不到。 - 多路径使用分号 `;` 分隔,不使用逗号分隔。 ### `syssettsllibpath()` 与 `sysgettsllibpath()` 代码块身份:配置片段 / 概念骨架 ```text writeLn(syssettsllibpath("C:/path/to/libA/;C:/path/to/libB/")); writeLn(sysgettsllibpath()); writeLn(call("FnA")); writeLn(call("FnB")); ``` 结果说明: - `syssettsllibpath(".../libA/;.../libB/")` 之后,`call("FnA")` 和 `call("FnB")` 都能找到对应函数。 - `sysgettsllibpath()` 返回设置后的完整路径串。 - 多路径使用分号 `;` 分隔;路径项保留结尾 `/` 或 `\`。 ### `tsl.conf` 的 `Libpath=` 代码块身份:配置片段 / 概念骨架 ```text [system] Libpath=D:\path\to\funcext\ ``` 结果说明: - `tsl.conf` 设为绝对 `Libpath=` 时,脚本可以找到该目录下的 `.tsf`。 - `Libpath={$P}funcext/` 不作为本页文档能力。 - `InheritParent=1` 不作为本页文档能力。 绝对 `Libpath=` 正例: 代码块身份:配置片段 / 概念骨架 ```text // tsl.conf [system] Libpath=D:/abs/path/to/funcext/ // funcext/Hello.tsf function Hello(); begin return 123; end; // main.tsl writeLn(Hello()); ``` 结果说明: - 上面这种绝对 `Libpath=` 配置下,`main.tsl` 输出 `123`。 `Libpath={$P}` 负例: 代码块身份:反例 / 不可照写 ```text // tsl.conf [system] Libpath={$P}funcext/ // funcext/Hello.tsf function Hello(); begin return 456; end; // main.tsl writeLn(Hello()); ``` 结果说明: - 上面这种 `Libpath={$P}funcext/` 配置下,`main.tsl` 仍报 `function:Hello compile error or not found`。 - 因此不能把 `{$P}` 替换写法当成文档规则。 `InheritParent=1` 的最小负例: 代码块身份:反例 / 不可照写 ```text // parent/tsl.conf [system] Namespace=ParentNS InheritParent=1 // parent/sub/Hello@ParentNS.tsf function Hello(); begin return 314; end; // parent/sub/main.tsl writeLn(Hello()); ``` 结果说明: - 这组父目录配置 + 子目录脚本的最小样例会报 `function:Hello compile error or not found` - 因此 `InheritParent=1` 不作为可写事实 ## 禁止项 - 把 `DemoUnit.var_name := value` 当成可用的限定赋值。 - 以为 `implementation` 里的常量和变量一定都不能从 `DemoUnit.Member` 读到。 - 以为 `findFunction("DemoUnit")` 暴露的成员范围和 `DemoUnit.Member` 完全相同。 - 把脚本内的 `namespace "..."` 当成和 `tsl.conf` 里的 `Namespace=...` 叠加,而不是覆盖。 - 把 `-LIBPATH` 放在脚本文件名前面。 - 用逗号分隔 `-LIBPATH` 或 `syssettsllibpath()` 的多路径。 - 忘了给 `-LIBPATH` 或 `syssettsllibpath()` 的目录项补结尾 `/` 或 `\`。 - 把 `Libpath={$P}...` 和 `InheritParent=1` 直接当成文档规则。