playbook/docs/tsl/syntax/22_namespace_libpath_and_un...

12 KiB

Namespace Libpath And Unit Runtime

文档类型:语法主线 是否可直接用于生成代码:仅部分 是否含已验证可执行示例:是 是否含已验证反例:是 遇到不确定时跳转到:10_units_and_scope.md23_object_runtime_and_introspection.md12_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 什么时候进入 initialization
  • finalization 在什么时候触发
  • unit 里的常量、变量和 findfunction("UnitName") 到底暴露哪些成员
  • tslfilename() 返回什么
  • NameSpace "..."tsl.conf-LIBPATH 怎样影响 .tsf 查找
  • syssettsllibpath() / sysgettsllibpath() 怎样在运行时改查找路径

必须记住的规则

  • 完整 unit 形态可以包含 interfaceimplementationinitializationfinalization,并以 end. 结束。
  • initializationunit 第一次被实际使用时触发,不是只因为顶层写了 uses 就立刻执行。
  • finalization 会在脚本结束前触发。
  • 直接写 DemoUnit.Member 时,当前解释器能读到 interfaceimplementation 里的常量、变量。
  • findfunction("DemoUnit") 拿到的是 unit 对象入口,但当前只验证到它稳定暴露 interface 成员;implementation 变量和常量没有从这个对象读出来。
  • DemoUnit.VarName := value 这种限定赋值当前会编译失败;如果要改 unit 状态,应导出函数或方法来改。
  • tslfilename() 在命令行解释器里返回当前执行脚本的完整路径。
  • NameSpace "DemoNS"; 会选择 Hello@DemoNS.tsf 这类命名空间函数文件。
  • tsl.conf[system] Namespace=... 可以设置默认命名空间;脚本里的 NameSpace "..." 会覆盖配置值。
  • 当全局 Hello.tsfHello@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.

已验证运行结果:

  • 输出顺序是 BEFOREINIT7110111FINAL
  • 这说明顶层写了 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.confLibpath=

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

[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 放在脚本文件名前面。
  • 用逗号分隔 -LIBPATHsyssettsllibpath() 的多路径。
  • 忘了给 -LIBPATHsyssettsllibpath() 的目录项补结尾 /\
  • Libpath={$P}...InheritParent=1 直接当成当前解释器已经稳定可依赖的规则。

跳转指引