# TSL 命名空间、Libpath 与 unit 运行时

这一篇收拢 `unit` 的运行时行为，以及 `.tsf` / `namespace` / `libpath` 的查找规则。`unit` 的基础写法、`uses` 位置和重名解析，统一放在 [09_units_and_scope.md](09_units_and_scope.md)。

## 本篇职责

<!-- section-id: syntax-18-001 -->

回答下面这些“写法已经会了，但运行起来为什么这样”的问题：

- `unit` 什么时候进入 `initialization`
- `finalization` 在什么时候触发
- `unit` 里的常量、变量和 `findFunction("UnitName")` 到底暴露哪些成员
- `tslfilename()` 返回什么
- `namespace "..."`、`tsl.conf` 和 `-LIBPATH` 怎样影响 `.tsf` 查找
- `syssettsllibpath()` / `sysgettsllibpath()` 怎样在运行时改查找路径

## 核心规则

<!-- section-id: syntax-18-002 -->

- 完整 `unit` 形态可以包含 `interface`、`implementation`、`initialization`、`finalization`，并以 `end.` 结束。
- `initialization` 在 `unit` 第一次被实际使用时触发，不是只因为顶层写了 `uses` 就立刻执行。
- `finalization` 会在脚本结束前触发。
- 跨版本安全边界只保证 `interface` 中声明的常量、变量和函数可由引用者访问；只在 `implementation` 中声明的成员按私有内容处理。
- `findFunction("DemoUnit")` 拿到的是 `unit` 对象入口；本页只把它稳定暴露 `interface` 成员写成文档事实。
- `DemoUnit.var_name := value` 这种限定赋值不作为可写事实；如果要改 `unit` 状态，应导出函数或方法来改。
- `tslfilename()` 的参数规格使用 `tsl-api-reference` skill 按名查询；本页只保留它返回正在执行的 `.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` 不作为本页文档能力。

## 示例与行为

<!-- section-id: syntax-18-003 -->

### `unit` 生命周期

<!-- section-id: syntax-18-004 -->

<!-- tags: 模块什么时候初始化, 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` 成员的读取边界

<!-- section-id: syntax-18-005 -->

<!-- tags: 读模块常量, 访问 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(PublicFunc());
```

结果说明：

- `DemoUnit.public_const` 输出 `1`。
- `DemoUnit.public_var` 输出 `3`。
- `PublicFunc()` 输出 `10`。
- 本页的跨版本文档边界是：外部只依赖 `interface` 引出的成员；实现段里的常量、变量和函数都视为私有。

部分新一代解释器允许用 `DemoUnit.impl_const` / `DemoUnit.impl_var` 限定读取实现段数据，但这与经典 `unit` 可见性规则冲突，不作为跨环境默认生成能力。若项目已经依赖该行为，必须先按目标解释器实测并记录版本。

实现段函数的外部调用反例：

代码块身份：反例 / 不可照写

```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()`

<!-- section-id: syntax-18-006 -->

<!-- tags: 当前脚本路径, 我在哪个文件, 取自身文件名 -->

代码块身份：可直接照写示例

```tsl
writeLn(tslfilename());
```

结果说明：

- 这一行会输出正在执行的 `.tsl` 主脚本完整路径。
- 本页只把“主脚本路径”写成文档事实；不要把 `.tsf` 被调用场景的返回值提前写成事实。

### `namespace` 与默认命名空间

<!-- section-id: syntax-18-007 -->

<!-- tags: 命名空间, 同名函数归属, 默认空间, 全局与命名空间冲突 -->

把默认命名空间、脚本覆盖、以及“全局函数与命名空间函数同名”的三组结论压成同一套最小实验：

代码块身份：配置片段 / 概念骨架

```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`

<!-- section-id: syntax-18-008 -->

<!-- tags: 查找路径, 找不到文件, 搜索顺序, 命令行指定路径 -->

查找顺序：

代码块身份：配置片段 / 概念骨架
代码块说明：命令行运行形态和多文件结构骨架；这些命令不可写进 `.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()`

<!-- section-id: syntax-18-009 -->

<!-- tags: 运行时改查找路径, 读取库路径 -->

代码块身份：配置片段 / 概念骨架

```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=`

<!-- section-id: syntax-18-010 -->

<!-- tags: 配置文件, 配路径, tsl.conf 怎么写 -->

代码块身份：配置片段 / 概念骨架

```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` 不作为可写事实

## 禁止项

<!-- section-id: syntax-18-011 -->

- 把 `DemoUnit.var_name := value` 当成可用的限定赋值。
- 默认从外部读取只在 `implementation` 中声明的成员；跨版本安全代码应通过 `interface` 导出。
- 以为 `findFunction("DemoUnit")` 暴露的成员范围和 `DemoUnit.Member` 完全相同。
- 把脚本内的 `namespace "..."` 当成和 `tsl.conf` 里的 `Namespace=...` 叠加，而不是覆盖。
- 把 `-LIBPATH` 放在脚本文件名前面。
- 用逗号分隔 `-LIBPATH` 或 `syssettsllibpath()` 的多路径。
- 忘了给 `-LIBPATH` 或 `syssettsllibpath()` 的目录项补结尾 `/` 或 `\`。
- 把 `Libpath={$P}...` 和 `InheritParent=1` 直接当成文档规则。
