12 KiB
Namespace Libpath And Unit Runtime
文档类型:语法主线 是否可直接用于生成代码:仅部分 是否含已验证可执行示例:是 是否含已验证反例:是 遇到不确定时跳转到:10_units_and_scope.md、23_object_runtime_and_introspection.md、12_pitfalls.md
手册位置:第 22 篇,共 32 篇。上一篇:21_external_calls_and_threads.md。下一篇:23_object_runtime_and_introspection.md。
这一篇收拢 unit 的运行时行为,以及 .tsf / namespace / libpath 的查找规则。unit 的基础写法、uses 位置和重名解析,统一放在 10_units_and_scope.md。
这一篇解决什么问题
回答下面这些“写法已经会了,但运行起来为什么这样”的问题:
unit什么时候进入initializationfinalization在什么时候触发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 生命周期
代码块身份:配置片段 / 概念骨架
// 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 成员的读取边界
直接限定读取:
代码块身份:配置片段 / 概念骨架
// 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 对象:
代码块身份:配置片段 / 概念骨架
// 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成员。
限定赋值失败:
代码块身份:反例 / 不可照写
uses DemoUnit;
DemoUnit.UnitCounter := 13;
已验证结果:
- 上面这段会编译失败,报错核心是
left side can not be assign to。
TslFileName
代码块身份:已验证可执行示例
WriteLn(tslfilename());
已验证结果:
- 在命令行解释器里,这一行会输出当前执行
.tsl文件的完整路径。 - 当前已验证的是“主脚本路径”;不要把
.tsf被调用场景的返回值提前写成已验证事实。
NameSpace 与默认命名空间
把默认命名空间、脚本覆盖、以及“全局函数与命名空间函数同名”的三组结论压成同一套最小实验:
代码块身份:配置片段 / 概念骨架
// 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
查找顺序:
代码块身份:配置片段 / 概念骨架
// 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从前到后查找,先命中的目录优先。
位置与分隔符边界:
代码块身份:配置片段 / 概念骨架
tsl -LIBPATH "D:\libA\" .\main.tsl
tsl .\main.tsl -LIBPATH "D:\libA"
tsl .\main.tsl -LIBPATH "D:\libA\,D:\libB\"
已验证结果:
- 把
-LIBPATH放在脚本文件名前面时,解释器不会执行脚本,而是回到 usage 提示。 - 路径项不带结尾
/或\时,函数找不到。 - 逗号分隔多路径没有跑通;当前实测有效的是分号
;。
syssettsllibpath() 与 sysgettsllibpath()
代码块身份:配置片段 / 概念骨架
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=
代码块身份:配置片段 / 概念骨架
[system]
Libpath=D:\path\to\funcext\
已验证结果:
- 当前目录
tsl.conf设为绝对Libpath=时,脚本可以找到该目录下的.tsf。 - 当前解释器下,
Libpath={$P}funcext/没有拿到正向结果。 - 当前解释器下,也没有拿到
InheritParent=1的稳定正例,因此不要把这两条当成可依赖规则。
Libpath= 的正向与负向对照:
代码块身份:配置片段 / 概念骨架
// 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 的最小负例:
代码块身份:反例 / 不可照写
// 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 - 查看对象反射与运行时对象:见 23_object_runtime_and_introspection.md
- 回看函数文件与顶层主体:见 03_core_model.md