12 KiB
TSL 命名空间、Libpath 与 unit 运行时
文档类型:语法深水专题 是否可直接用于生成代码:仅部分 是否含可直接照写示例:是 是否含不可照写反例:是 遇到不确定时:先按本页候选页继续判断;09_units_and_scope.md、20_object_runtime_and_introspection.md、11_pitfalls.md;仍不命中时回到语法路由中心 index.md;如果问题已经超出语法层,回到 TSL 总入口 ../index.md
这一篇收拢 unit 的运行时行为,以及 .tsf / namespace / libpath 的查找规则。unit 的基础写法、uses 位置和重名解析,统一放在 09_units_and_scope.md。
本篇职责
回答下面这些“写法已经会了,但运行起来为什么这样”的问题:
unit什么时候进入initializationfinalization在什么时候触发unit里的常量、变量和findFunction("UnitName")到底暴露哪些成员tslfilename()返回什么namespace "..."、tsl.conf和-LIBPATH怎样影响.tsf查找syssettsllibpath()/sysgettsllibpath()怎样在运行时改查找路径
智能体命名空间/Libpath 判断流程
- 先判断任务是单元命名空间访问、
-LIBPATH查找,还是配置文件查找。 - 路径和查找顺序只写平台中立规则,不写某个开发机或容器路径。
-LIBPATH只作为运行时部署/查找规则,不写进普通语法示例。Libpath={$P}...和InheritParent=1不作为本页文档能力。- 没有对应代码块时不要发明命名空间/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;本页只保留它返回正在执行的.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 生命周期
代码块身份:配置片段 / 概念骨架
// 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在脚本主体输出结束后触发。
代码块身份:输出片段
BEFORE
INIT
7
1
101
11
FINAL
unit 成员的读取边界
直接限定读取:
代码块身份:配置片段 / 概念骨架
// 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直接读取。
实现段函数的外部调用反例:
代码块身份:反例 / 不可照写
uses DemoUnit;
writeLn(ImplFunc());
结果说明:
- 上面这种写法会报
function:ImplFunc compile error or not found。 - 因此不要把“实现段常量和变量可限定读取”泛化成“实现段函数可外部调用”。
findFunction("UnitName") 拿到的 unit 对象:
代码块身份:配置片段 / 概念骨架
// 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") 对象入口访问实现段成员反例:
代码块身份:反例 / 不可照写
uses DemoUnit;
u := findFunction("DemoUnit");
writeLn(u.impl_const);
结果说明:
- 上面这种写法会报对象属性或方法不存在。
- 因此不要把
findFunction("DemoUnit")的对象入口等同于DemoUnit.Member限定访问。
限定赋值失败:
代码块身份:反例 / 不可照写
uses DemoUnit;
DemoUnit.unit_counter := 13;
结果说明:
- 上面这段会编译失败,报错核心是
left side can not be assign to。
tslfilename()
代码块身份:可直接照写示例
writeLn(tslfilename());
结果说明:
- 这一行会输出正在执行的
.tsl主脚本完整路径。 - 本页只把“主脚本路径”写成文档事实;不要把
.tsf被调用场景的返回值提前写成事实。
namespace 与默认命名空间
把默认命名空间、脚本覆盖、以及“全局函数与命名空间函数同名”的三组结论压成同一套最小实验:
代码块身份:配置片段 / 概念骨架
// 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 源码。
// 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 源码。
tsl -LIBPATH "D:\libA\" .\main.tsl
tsl .\main.tsl -LIBPATH "D:\libA"
tsl .\main.tsl -LIBPATH "D:\libA\,D:\libB\"
结果说明:
- 把
-LIBPATH放在脚本文件名前面时,解释器不会执行脚本,而是回到 usage 提示。 - 路径项不带结尾
/或\时,函数找不到。 - 多路径使用分号
;分隔,不使用逗号分隔。
syssettsllibpath() 与 sysgettsllibpath()
代码块身份:配置片段 / 概念骨架
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=
代码块身份:配置片段 / 概念骨架
[system]
Libpath=D:\path\to\funcext\
结果说明:
tsl.conf设为绝对Libpath=时,脚本可以找到该目录下的.tsf。Libpath={$P}funcext/不作为本页文档能力。InheritParent=1不作为本页文档能力。
绝对 Libpath= 正例:
代码块身份:配置片段 / 概念骨架
// 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} 负例:
代码块身份:反例 / 不可照写
// 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 的最小负例:
代码块身份:反例 / 不可照写
// 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直接当成文档规则。