📝 docs(tsl): rebuild canonical syntax and routing manual
This commit is contained in:
@@ -0,0 +1,425 @@
|
||||
# Namespace Libpath And Unit Runtime
|
||||
|
||||
文档类型:语法主线
|
||||
是否可直接用于生成代码:仅部分
|
||||
是否含已验证可执行示例:是
|
||||
是否含已验证反例:是
|
||||
遇到不确定时跳转到:[10_units_and_scope.md](10_units_and_scope.md)、[23_object_runtime_and_introspection.md](23_object_runtime_and_introspection.md)、[12_pitfalls.md](12_pitfalls.md)
|
||||
|
||||
手册位置:第 22 篇,共 32 篇。上一篇:[21_external_calls_and_threads.md](21_external_calls_and_threads.md)。下一篇:[23_object_runtime_and_introspection.md](23_object_runtime_and_introspection.md)。
|
||||
|
||||
这一篇收拢 `unit` 的运行时行为,以及 `.tsf` / `namespace` / `libpath` 的查找规则。`unit` 的基础写法、`uses` 位置和重名解析,统一放在 [10_units_and_scope.md](10_units_and_scope.md)。
|
||||
|
||||
## 这一篇解决什么问题
|
||||
|
||||
回答下面这些“写法已经会了,但运行起来为什么这样”的问题:
|
||||
|
||||
- `unit` 什么时候进入 `initialization`
|
||||
- `finalization` 在什么时候触发
|
||||
- `unit` 里的常量、变量和 `findfunction("UnitName")` 到底暴露哪些成员
|
||||
- `tslfilename()` 返回什么
|
||||
- `NameSpace "..."`、`tsl.conf` 和 `-LIBPATH` 怎样影响 `.tsf` 查找
|
||||
- `syssettsllibpath()` / `sysgettsllibpath()` 怎样在运行时改查找路径
|
||||
|
||||
## 必须记住的规则
|
||||
|
||||
- 完整 `unit` 形态可以包含 `interface`、`implementation`、`initialization`、`finalization`,并以 `end.` 结束。
|
||||
- `initialization` 在 `unit` 第一次被实际使用时触发,不是只因为顶层写了 `uses` 就立刻执行。
|
||||
- `finalization` 会在脚本结束前触发。
|
||||
- 直接写 `DemoUnit.Member` 时,当前解释器能读到 `interface` 和 `implementation` 里的常量、变量。
|
||||
- `findfunction("DemoUnit")` 拿到的是 `unit` 对象入口,但当前只验证到它稳定暴露 `interface` 成员;`implementation` 变量和常量没有从这个对象读出来。
|
||||
- `DemoUnit.VarName := value` 这种限定赋值当前会编译失败;如果要改 `unit` 状态,应导出函数或方法来改。
|
||||
- `tslfilename()` 在命令行解释器里返回当前执行脚本的完整路径。
|
||||
- `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 UnitValue = 7;
|
||||
var UnitCounter;
|
||||
function Ping();
|
||||
implementation
|
||||
var PrivateCounter;
|
||||
function Ping();
|
||||
begin
|
||||
PrivateCounter := PrivateCounter + 1;
|
||||
UnitCounter := UnitCounter + 10;
|
||||
return PrivateCounter;
|
||||
end;
|
||||
initialization
|
||||
WriteLn("INIT");
|
||||
UnitCounter := 1;
|
||||
PrivateCounter := 100;
|
||||
finalization
|
||||
WriteLn("FINAL");
|
||||
end.
|
||||
|
||||
// main.tsl
|
||||
program test;
|
||||
uses DemoUnit;
|
||||
begin
|
||||
WriteLn("BEFORE");
|
||||
WriteLn(DemoUnit.UnitValue);
|
||||
WriteLn(DemoUnit.UnitCounter);
|
||||
WriteLn(Ping());
|
||||
WriteLn(DemoUnit.UnitCounter);
|
||||
end.
|
||||
```
|
||||
|
||||
已验证运行结果:
|
||||
|
||||
- 输出顺序是 `BEFORE`、`INIT`、`7`、`1`、`101`、`11`、`FINAL`。
|
||||
- 这说明顶层写了 `uses DemoUnit;` 之后,`initialization` 不是立刻执行,而是在第一次真正读 `DemoUnit` 成员时触发。
|
||||
- `finalization` 在脚本主体输出结束后触发。
|
||||
|
||||
### `unit` 成员的读取边界
|
||||
|
||||
直接限定读取:
|
||||
|
||||
代码块身份:配置片段 / 概念骨架
|
||||
|
||||
```text
|
||||
// DemoUnit.tsf
|
||||
unit DemoUnit;
|
||||
interface
|
||||
const PublicConst = 1;
|
||||
var PublicVar;
|
||||
function PublicFunc();
|
||||
implementation
|
||||
const ImplConst = 2;
|
||||
var ImplVar;
|
||||
function PublicFunc();
|
||||
begin
|
||||
return 10;
|
||||
end;
|
||||
function ImplFunc();
|
||||
begin
|
||||
return 20;
|
||||
end;
|
||||
initialization
|
||||
PublicVar := 3;
|
||||
ImplVar := 4;
|
||||
end.
|
||||
|
||||
// main.tsl
|
||||
uses DemoUnit;
|
||||
WriteLn(DemoUnit.PublicConst);
|
||||
WriteLn(DemoUnit.PublicVar);
|
||||
WriteLn(DemoUnit.ImplConst);
|
||||
WriteLn(DemoUnit.ImplVar);
|
||||
WriteLn(PublicFunc());
|
||||
WriteLn(ImplFunc());
|
||||
```
|
||||
|
||||
已验证运行结果:
|
||||
|
||||
- `DemoUnit.PublicConst` 输出 `1`。
|
||||
- `DemoUnit.PublicVar` 输出 `3`。
|
||||
- `DemoUnit.ImplConst` 输出 `2`。
|
||||
- `DemoUnit.ImplVar` 输出 `4`。
|
||||
- `PublicFunc()` 输出 `10`。
|
||||
- `ImplFunc()` 仍然报 `compile error or not found`。
|
||||
- 当前解释器的真实边界是:实现段函数仍私有,但实现段常量和变量可以通过 `DemoUnit.Member` 直接读取。
|
||||
|
||||
`findfunction("UnitName")` 拿到的 `unit` 对象:
|
||||
|
||||
代码块身份:配置片段 / 概念骨架
|
||||
|
||||
```text
|
||||
// DemoUnit.tsf
|
||||
unit DemoUnit;
|
||||
interface
|
||||
const PublicConst = 1;
|
||||
var PublicVar;
|
||||
function Ping();
|
||||
implementation
|
||||
const ImplConst = 2;
|
||||
var ImplVar;
|
||||
function Ping();
|
||||
begin
|
||||
return 11;
|
||||
end;
|
||||
initialization
|
||||
PublicVar := 3;
|
||||
ImplVar := 4;
|
||||
end.
|
||||
|
||||
// main.tsl
|
||||
uses DemoUnit;
|
||||
u := findfunction("DemoUnit");
|
||||
WriteLn(u.PublicConst);
|
||||
WriteLn(u.PublicVar);
|
||||
WriteLn(u.Ping());
|
||||
WriteLn(u.ImplConst);
|
||||
```
|
||||
|
||||
已验证运行结果:
|
||||
|
||||
- `u.PublicConst` 输出 `1`。
|
||||
- `u.PublicVar` 输出 `3`。
|
||||
- `u.Ping()` 输出 `11`。
|
||||
- `u.ImplConst` 报对象属性或方法不存在。
|
||||
- 当前解释器下,`findfunction("DemoUnit")` 拿到的对象入口只稳定暴露 `interface` 成员。
|
||||
|
||||
限定赋值失败:
|
||||
|
||||
代码块身份:反例 / 不可照写
|
||||
|
||||
```text
|
||||
uses DemoUnit;
|
||||
DemoUnit.UnitCounter := 13;
|
||||
```
|
||||
|
||||
已验证结果:
|
||||
|
||||
- 上面这段会编译失败,报错核心是 `left side can not be assign to`。
|
||||
|
||||
### `TslFileName`
|
||||
|
||||
代码块身份:已验证可执行示例
|
||||
|
||||
```tsl
|
||||
WriteLn(tslfilename());
|
||||
```
|
||||
|
||||
已验证结果:
|
||||
|
||||
- 在命令行解释器里,这一行会输出当前执行 `.tsl` 文件的完整路径。
|
||||
- 当前已验证的是“主脚本路径”;不要把 `.tsf` 被调用场景的返回值提前写成已验证事实。
|
||||
|
||||
### `NameSpace` 与默认命名空间
|
||||
|
||||
把默认命名空间、脚本覆盖、以及“全局函数与命名空间函数同名”的三组结论压成同一套最小实验:
|
||||
|
||||
代码块身份:配置片段 / 概念骨架
|
||||
|
||||
```text
|
||||
// tsl.conf
|
||||
[system]
|
||||
Namespace=ConfNS
|
||||
Libpath=D:\path\to\lib\
|
||||
|
||||
// 同一目录下准备三份函数文件
|
||||
// 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
|
||||
program test;
|
||||
begin
|
||||
WriteLn(Hello());
|
||||
end.
|
||||
|
||||
// main_stmt.tsl
|
||||
program test;
|
||||
begin
|
||||
NameSpace "StmtNS";
|
||||
WriteLn(Hello());
|
||||
end.
|
||||
```
|
||||
|
||||
已验证运行结果:
|
||||
|
||||
- `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`
|
||||
|
||||
查找顺序:
|
||||
|
||||
代码块身份:配置片段 / 概念骨架
|
||||
|
||||
```text
|
||||
// libA/Hello.tsf
|
||||
function Hello();
|
||||
begin
|
||||
return 101;
|
||||
end;
|
||||
|
||||
// libB/Hello.tsf
|
||||
function Hello();
|
||||
begin
|
||||
return 202;
|
||||
end;
|
||||
|
||||
// main.tsl
|
||||
program test;
|
||||
begin
|
||||
WriteLn(Hello());
|
||||
end.
|
||||
|
||||
// command A
|
||||
tsl .\main.tsl -LIBPATH "D:\libA\;D:\libB\"
|
||||
|
||||
// command B
|
||||
tsl .\main.tsl -LIBPATH "D:\libB\;D:\libA\"
|
||||
```
|
||||
|
||||
已验证运行结果:
|
||||
|
||||
- 命令 A 输出 `101`。
|
||||
- 命令 B 输出 `202`。
|
||||
- 当前解释器按 `-LIBPATH` 从前到后查找,先命中的目录优先。
|
||||
|
||||
位置与分隔符边界:
|
||||
|
||||
代码块身份:配置片段 / 概念骨架
|
||||
|
||||
```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
|
||||
program test;
|
||||
begin
|
||||
WriteLn(syssettsllibpath("C:/path/to/libA/;C:/path/to/libB/"));
|
||||
WriteLn(sysgettsllibpath());
|
||||
WriteLn(call("FnA"));
|
||||
WriteLn(call("FnB"));
|
||||
end.
|
||||
```
|
||||
|
||||
已验证运行结果:
|
||||
|
||||
- `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
|
||||
// case A: 绝对 Libpath
|
||||
// tsl.conf
|
||||
[system]
|
||||
Libpath=D:/abs/path/to/funcext/
|
||||
|
||||
// funcext/Hello.tsf
|
||||
function Hello();
|
||||
begin
|
||||
return 123;
|
||||
end;
|
||||
|
||||
// main.tsl
|
||||
program test;
|
||||
begin
|
||||
WriteLn(Hello());
|
||||
end.
|
||||
|
||||
// case B: {$P} Libpath
|
||||
// tsl.conf
|
||||
[system]
|
||||
Libpath={$P}funcext/
|
||||
|
||||
// case B 的 main.tsl 与 case A 相同
|
||||
// case B 的 funcext/Hello.tsf 只把返回值改成 456
|
||||
```
|
||||
|
||||
已验证结果:
|
||||
|
||||
- case A 输出 `123`
|
||||
- case B 仍报 `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
|
||||
program test;
|
||||
begin
|
||||
WriteLn(Hello());
|
||||
end.
|
||||
```
|
||||
|
||||
已验证结果:
|
||||
|
||||
- 在当前解释器里,这组父目录配置 + 子目录脚本的最小样例仍报 `function:Hello compile error or not found`
|
||||
- 因此 `InheritParent=1` 现在还不能升级成正式正向结论
|
||||
|
||||
## 常见误写
|
||||
|
||||
- 把 `DemoUnit.VarName := value` 当成可用的限定赋值。
|
||||
- 以为 `implementation` 里的常量和变量一定都不能从 `DemoUnit.Member` 读到。
|
||||
- 以为 `findfunction("DemoUnit")` 暴露的成员范围和 `DemoUnit.Member` 完全相同。
|
||||
- 把脚本内的 `NameSpace "..."` 当成和 `tsl.conf` 里的 `Namespace=...` 叠加,而不是覆盖。
|
||||
- 把 `-LIBPATH` 放在脚本文件名前面。
|
||||
- 用逗号分隔 `-LIBPATH` 或 `syssettsllibpath()` 的多路径。
|
||||
- 忘了给 `-LIBPATH` 或 `syssettsllibpath()` 的目录项补结尾 `/` 或 `\`。
|
||||
- 把 `Libpath={$P}...` 和 `InheritParent=1` 直接当成当前解释器已经稳定可依赖的规则。
|
||||
|
||||
## 跳转指引
|
||||
|
||||
- 回看 `unit` / `uses` 主线:见 [10_units_and_scope.md](10_units_and_scope.md)
|
||||
- 查看对象反射与运行时对象:见 [23_object_runtime_and_introspection.md](23_object_runtime_and_introspection.md)
|
||||
- 回看函数文件与顶层主体:见 [03_core_model.md](03_core_model.md)
|
||||
Reference in New Issue
Block a user