diff --git a/skills/tsl-syntax-reference/SKILL.md b/skills/tsl-syntax-reference/SKILL.md index a4441ff6..56513b2d 100644 --- a/skills/tsl-syntax-reference/SKILL.md +++ b/skills/tsl-syntax-reference/SKILL.md @@ -58,9 +58,9 @@ python /scripts/lookup.py --section "syntax-05-004" "syntax-02-0 - 中文口语词自动扩展到 TSL 术语(如"打印"→输出/writeLn,"列表"→数组, "程序慢"→性能分析)。保留提取词的原写法即可。 -- `tsl`、`tsf`、`tinysoft`、`program`、`debug`、`please` 这几个词不参与逐词匹配, +- `tsl`、`tsf`、`tinysoft`、`debug`、`please` 这几个词不参与逐词匹配, 但可能整体把查询导向某个专题页。无结果时靠加这类词补救没有用,改为换更具体的 - 语法要素名。 + 语法要素名。`program` 是真实 TSL 关键字,会参与精确匹配。 ## 弱命中视同无匹配 diff --git a/skills/tsl-syntax-reference/data/lexicon.json b/skills/tsl-syntax-reference/data/lexicon.json index f0c53bdf..7fbc408f 100644 --- a/skills/tsl-syntax-reference/data/lexicon.json +++ b/skills/tsl-syntax-reference/data/lexicon.json @@ -31,23 +31,42 @@ "函数指针释放": ["DeleteInstance", "MakeInstance"], "Linux 动态加载": ["dlopen", "dlsym", ".so"], "远程调用客户端": ["RDo", "RDo2", "客户端远程调用"], - "本地弹窗": ["RDo2", "InputQuery", "客户端远程调用"] + "本地弹窗": ["RDo2", "InputQuery", "客户端远程调用"], + "全局变量": ["global", "跨函数共享变量"], + "静态计算": ["static", "表达式缓存"], + "只计算一次": ["static", "表达式缓存"], + "指定系统函数": ["system", "系统函数限定"], + "内存上限": ["_maxMem_", "最大内存"], + "每列聚集": ["avgof", "多字段聚集"], + "按行广播": ["二维矩阵", "一维数组", "行广播"] }, "page_intent_aliases": { "01_quickstart.md": ["最简单能跑", "最简单的脚本", "天软脚本", "tinysoft"], "02_core_model.md": [ "脚本和可复用", "可复用函数文件", - "声明函数后面写代码" + "声明函数后面写代码", + "完整 program 入口", + "echo 输出多个值" ], "03_values_and_literals.md": [ "字符串和数组下标", "下标从几开始", "下标起点" ], - "04_variables_and_constants.md": ["常量怎么声明", "变量能不能直接赋值"], - "05_functions_and_calls.md": ["默认参数", "函数怎么带"], - "06_expressions_and_operators.md": ["赋值和相等比较"], + "04_variables_and_constants.md": [ + "常量怎么声明", + "变量能不能直接赋值", + "全局变量", + "运行时常量" + ], + "05_functions_and_calls.md": ["默认参数", "函数怎么带", "指定系统函数"], + "06_expressions_and_operators.md": [ + "赋值和相等比较", + "运算符优先级", + "静态计算", + "只计算一次" + ], "07_control_flow.md": [ "跳出去", "循环里满足条件", @@ -59,7 +78,12 @@ "08_objects_and_classes.md": ["定义类", "创建对象"], "09_units_and_scope.md": ["多个文件", "复用一组函数"], "10_runtime_context_and_with.md": ["临时切换系统参数"], - "11_matrix_and_collections.md": ["某行存在", "二维数组怎么判断"], + "11_matrix_and_collections.md": [ + "某行存在", + "二维数组怎么判断", + "按行广播", + "二维矩阵乘一维数组" + ], "12_resultset_and_filters.md": ["保留匹配行", "按某一列"], "13_ts_sql.md": [ "左连接", @@ -67,7 +91,9 @@ "左联接", "数据库", "分组排序", - "聚合排序" + "聚合排序", + "JOIN 第二张表当前行", + "每列聚集" ], "14_debug_and_profiler.md": [ "程序慢", @@ -77,11 +103,13 @@ "执行了多久", "执行时间", "耗时", - "debug" + "debug", + "内存上限" ], "15_lexical_structure_and_compile_options.md": [ "变量名区分大小写", - "注释怎么写" + "注释怎么写", + "嵌套注释" ], "16_types_and_conversions.md": ["字符串转整数", "类型转换"], "17_external_calls_and_threads.md": [ diff --git a/skills/tsl-syntax-reference/references/02_core_model.md b/skills/tsl-syntax-reference/references/02_core_model.md index d10f7134..58cac80b 100644 --- a/skills/tsl-syntax-reference/references/02_core_model.md +++ b/skills/tsl-syntax-reference/references/02_core_model.md @@ -6,7 +6,7 @@ -回答“目标文件到底是 `.tsl` 脚本还是 `.tsf` 可复用声明文件,以及 `.tsl` 里的哪些内容会顺序执行、哪些内容只是后置声明”。 +回答“目标文件到底是 `.tsl` 脚本还是 `.tsf` 可复用声明文件,`.tsl` 里的哪些内容会顺序执行、哪些内容只是后置声明,以及何时使用完整 `program ... begin ... end.` 入口形态”。 本页是文件模型的唯一事实源:后缀判断、语句区 / 声明区顺序、`.tsf` 顶层声明形态和文件名约束都在这里收口。函数体、类体、`unit` 内部的语法外形由各自专题页拥有。 @@ -33,6 +33,7 @@ - `uses` 可以出现在顶层,但这里只把它当成辅助语句,不把它当成主体声明;函数体和类定义体里的位置限制见 [09_units_and_scope.md](09_units_and_scope.md)。 - 裸 `class Name` 不作为类定义写法使用。 - 在 `.tsl` 中,不要在声明区之后继续追加脚本语句。 +- `program Name; ... begin ... end.` 是完整程序入口形态;它可以在主 `begin` 前声明子函数,但不是普通 `.tsl` 脚本的默认起手式。 - `unit` 默认先按完整形态理解;它也可以省略 `interface` / `implementation` 写成简写形态,见 [09_units_and_scope.md](09_units_and_scope.md)。 - 不要把 `.tsl` 写成只有顶层函数的模块;如果用户要通用可复用函数,优先写 `.tsf`。 - 不要把 `.tsf` 写成会直接执行脚本语句的入口;如果用户要顺序执行入口,优先写 `.tsl`。 @@ -184,6 +185,64 @@ end. 1 ``` +### 完整 `program` 入口形态 + + + + + +普通 `.tsl` 默认仍使用前面的松散语句区模型。只有用户明确要求完整程序入口、独立脚本 / CGI 兼容形态,或现有代码已经使用 `program` 时,才写成下面的结构: + +代码块身份:可直接照写示例 + +```tsl +program DemoProgram; + +function DoubleValue(value); +begin + return value * 2; +end; + +begin + echo "result:", DoubleValue(3); +end. +``` + +代码块身份:输出片段 + +```text +result:6 +``` + +说明: + +- `program Name;` 位于文件开头 +- 子函数声明位于主 `begin ... end.` 之前 +- 完整程序以 `end.` 结束,而普通函数声明仍以 `end;` 结束 +- 不要把这套结构与“松散语句区在前、声明区在后”的默认 `.tsl` 模型混写 + +### `echo` 输出语句 + + + + + +`echo expr1, expr2, ...;` 会按从左到右的顺序输出表达式列表。简单值直接输出;数组、对象等复杂值可能只显示类型摘要。具体输出目标由宿主环境决定,可能是控制台、Web 响应或客户端输出窗口。 + +代码块身份:可直接照写示例 + +```tsl +echo "A=", 1, ",B=", 2; +``` + +代码块身份:输出片段 + +```text +A=1,B=2 +``` + +不要把 `echo` 的宿主输出位置或复杂对象的展示格式当成跨环境固定结果。 + ### 文件模型反例 @@ -262,5 +321,6 @@ function:__main__:line 9: invalid statement - 把 `.tsl` 当成 `.tsf` 来写,只给一个顶层函数,不写任何会执行的脚本语句。 - 把 `.tsf` 当成 `.tsl` 来写,在模块文件里直接堆顺序执行的脚本语句。 +- 把 `program ... begin ... end.` 与松散 `.tsl` 语句区 / 后置声明区混成一套文件结构。 - 把 `uses` 当成主体声明,而不是辅助组织语句。 - `.tsf` 文件名与顶层声明不一致:`UserAccount.tsf` 中写 `function GetUser` 或 `type Customer = class` 或 `unit CustomerModule` 会导致加载失败或检索混乱。 diff --git a/skills/tsl-syntax-reference/references/03_values_and_literals.md b/skills/tsl-syntax-reference/references/03_values_and_literals.md index 3f2ef8b0..740fe863 100644 --- a/skills/tsl-syntax-reference/references/03_values_and_literals.md +++ b/skills/tsl-syntax-reference/references/03_values_and_literals.md @@ -24,6 +24,7 @@ - `array(...)` 既可以写顺序数组,也可以写字符串键表;顺序数组和 `binary(...)` 二进制缓冲区下标从 `0` 开始,字符串下标从 `1` 开始。 +- 数组元素可以是混合类型;给不存在的下标赋值会扩张数组。显式整数键可写成 `array(0: value0, 1: value1)`。 - `s[0]` 在运行时会越界,不要把字符串当成 0 基下标。 - 字符串取子串用 `s[start:end]`,并且 `end` 是包含在结果里的。 - 字符串替换子串用 `s[start:end] := "..."`。 @@ -36,6 +37,8 @@ - `U""` 不是宽串;需要在 UTF8、宽串、普通串之间显式转换时,继续用 `utf8ToUnicode(...)`、`utf8ToAnsi(...)`、`ansiToUtf8(...)`、`unicodeToUtf8(...)`、`string(...)`、`wideString(...)`。 - `#number` 可以直接把字符码拼进字符串。 - `\0` 和 `#0` 都能把 ASCII `0` 放进字符串,并且不会把字符串截断。 +- 字符串 `like` 的右侧按正则表达式解释,不是 SQL `%` / `_` 通配,也不要把正则里的 `*` 当成独立的 glob 通配符;默认匹配不区分大小写。 +- 数值 `like` 表示近似相等;`likeEps` 和 `likeEpsRate` 分别控制绝对误差与相对误差阈值,默认值都是 `1e-6`。 ## 可直接照写示例 @@ -170,6 +173,33 @@ C 代码块说明:顺序数组 `items` 从 `0` 开始,字符串键表 `row` 用字符串键访问,字符串 `s` 从 `1` 开始。 +数组按写入下标扩张: + +代码块身份:可直接照写示例 + +```tsl +items := array(1); +items[3] := 4; +keyed := array(0: 2, 1: 3); +writeLn(length(items)); +writeLn(items[1] = nil); +writeLn(items[3]); +writeLn(keyed[0]); +writeLn(keyed[1]); +``` + +代码块身份:输出片段 + +```text +2 +1 +4 +2 +3 +``` + +说明:`length(items)` 统计已有项数,不等于“最大整数键 + 1”;下标 `1` 没有写入,因此读取为 `nil`。 + ### 字符串边界规则 @@ -416,20 +446,20 @@ A=1 B=2.34 C=AAA - 纯小数可能走科学计数法:`"_" $ 0.0000005 $ ","` 返回 `"_5E-7,"` - 带整数部分的小数保留精度:`"_" $ 1.0000005 $ ","` 返回 `"_1.0000005,"` -### 字符串 `like` 模式匹配 +### 字符串与数值 `like` - + -`like` 用于判断字符串是否符合指定模式(支持通配符和正则表达式): +字符串 `like` 的右侧是正则表达式;默认匹配不区分大小写: 代码块身份:可直接照写示例 ```tsl -result1 := "hello" like "he*"; -result2 := "HELLO" like "he*"; -result3 := "test@example.com" like "*@*"; +result1 := "a" like "A"; +result2 := "HELLO" like "hello"; +result3 := "2009-1-1" like "\\d+-\\d+-\\d+"; writeLn("result1:", result1); writeLn("result2:", result2); writeLn("result3:", result3); @@ -439,22 +469,49 @@ writeLn("result3:", result3); ```text result1: 1 -result2: 0 +result2: 1 result3: 1 ``` 说明: -- `like` 大小写敏感,`"hello" like "he*"` 返回 `1`(真),`"HELLO" like "he*"` 返回 `0`(假) -- `*` 是通配符,匹配任意字符序列 -- `like` 也支持正则表达式模式(如 `"\\d{4}-\\d{2}-\\d{2}"` 匹配日期格式) +- 默认情况下,`"a" like "A"` 和 `"HELLO" like "hello"` 都返回 `1`(真) +- `"\\d+-\\d+-\\d+"` 是正则表达式,匹配示例中的日期形态 +- 正则量词 `*` 只修饰它前面的原子;不要把 `"he*"` 解释成“`he` 后面任意字符”的 glob 模式,需要任意字符序列时写成 `.*` +- 匹配控制标志可以改变大小写规则;控制标志的精确 API、参数与目标 scope 使用 `tsl-api-reference` skill 核对 + +数值 `like` 用于近似相等判断: + +代码块身份:可直接照写示例 + +```tsl +old_abs_eps := likeEps; +old_rate_eps := likeEpsRate; +likeEps := 0.1; +likeEpsRate := 0; +writeLn(1.05 like 1); +likeEps := old_abs_eps; +likeEpsRate := old_rate_eps; +``` + +代码块身份:输出片段 + +```text +1 +``` + +说明: + +- `likeEps` 和 `likeEpsRate` 都可读写,默认值均为 `1e-6` +- 两数绝对差小于 `likeEps`,或“绝对差 / 两数绝对值的平均值”小于 `likeEpsRate` 时,数值 `like` 返回真 +- 临时修改阈值后要恢复旧值,避免影响同一运行时中的后续比较 `not like` 是取反形式(TSL 2025/8 版本起支持): 代码块身份:可直接照写示例 ```tsl -result := "abc" not like "xyz*"; +result := "abc" not like "xyz.*"; writeLn(result); ``` @@ -547,6 +604,10 @@ items := array(1, 2, 3); - 把普通字符串里的 `\uXXXX` 直接当成宽串单字符。 - 把 `U""` 当成宽串;需要宽串时按本页转换链处理。 - 把 `#0` / `\0` 当成 C 风格字符串终止符。 +- 把字符串 `like` 误写成默认区分大小写;默认行为是不区分大小写。 +- 把 `like` 只理解成字符串模式匹配,遗漏数值近似相等语义。 +- 修改 `likeEps` / `likeEpsRate` 后不恢复,导致后续代码继承意外的比较阈值。 +- 以为给较大整数下标赋值后,中间所有位置都会自动生成实际元素;未写入的位置仍为 `nil`。 代码块身份:反例 / 不可照写 diff --git a/skills/tsl-syntax-reference/references/04_variables_and_constants.md b/skills/tsl-syntax-reference/references/04_variables_and_constants.md index f024fd61..defe5a08 100644 --- a/skills/tsl-syntax-reference/references/04_variables_and_constants.md +++ b/skills/tsl-syntax-reference/references/04_variables_and_constants.md @@ -6,7 +6,7 @@ -回答“普通变量怎样直接使用、`var` 在什么位置出现、常量必须怎样初始化、哪些名字一旦绑定就不能再赋值”。 +回答“普通变量怎样直接使用、`var` 在什么位置出现、常量必须怎样初始化、`global` 怎样跨函数共享,以及运行时常量怎样冻结一次调用中的表达式结果”。 ## 核心规则 @@ -27,6 +27,8 @@ - 右侧数组元素也可以是数组;拆出来的变量会直接得到对应子数组。 - 多参数赋值也可以出现在函数调用参数里。 - `{$explicit+}` 开启后,后续变量必须先用 `var` 声明;未声明变量会报 `variable not defined`。 +- `global x, y;` 声明当前任务中的全局变量;每个需要访问它的函数都要再次写 `global` 声明。 +- `const name := expression;` 是运行时常量:进入所属函数时计算,当前调用内不可再次赋值;不要与编译时常量 `const name = expression;` 混写。 ## 可直接照写示例 @@ -178,6 +180,71 @@ const kMaxRetries = 3 + 4; value := kMaxRetries; ``` +### 全局变量 `global` + + + + + +`global` 让同一运行任务中的不同函数访问同一变量。每个需要读写该变量的函数都要声明它;漏写时,同名标识符会按局部变量处理。 + +代码块身份:可直接照写示例 + +```tsl +global shared_value; +shared_value := 37; +writeLn(ReadShared()); + +function ReadShared(); +begin + global shared_value; + return shared_value; +end; +``` + +代码块身份:输出片段 + +```text +37 +``` + +本节只拥有“当前任务内跨函数共享”和“每个引用函数都要声明”的规则;不要从中推断跨任务持久化、线程隔离或进程级生命周期。 + +### 运行时常量 `const name := expression` + + + + + +运行时常量使用 `:=` 初始化,可以依赖参数、变量或函数调用。它在每次进入所属函数时重新计算一次,随后在本次调用内不可修改。 + +代码块身份:可直接照写示例 + +```tsl +writeLn(FreezeValue(2)); +writeLn(FreezeValue(5)); + +function FreezeValue(value); +begin + const fixed := value + 1; + return fixed; +end; +``` + +代码块身份:输出片段 + +```text +3 +6 +``` + +边界: + +- 编译时常量写 `const name = expression;`;运行时常量写 `const name := expression;` +- 运行时常量声明放在函数 `begin ... end` 语句体中,不放在函数头后的 `const` 声明段 +- 运行时常量不作为类成员写法 +- 初始化后再次赋值会破坏常量约束,不要生成 + ### 多参数赋值 @@ -331,6 +398,9 @@ items := array(1, 2, 3); - 以为 `const` 可以只声明名字,不写初始化表达式。 - 以为单变量拆包可以写成 `[a] := array(...)`。 - 以为 `{$explicit+}` 开启后仍然可以继续直接写未声明变量。 +- 只在脚本顶层声明一次 `global`,却忘记在读取它的函数里再次声明。 +- 把运行时常量 `const name := expression;` 写进函数头后的编译时 `const` 声明段。 +- 把运行时常量当成可重新赋值的普通变量或类成员。 代码块身份:反例 / 不可照写 diff --git a/skills/tsl-syntax-reference/references/05_functions_and_calls.md b/skills/tsl-syntax-reference/references/05_functions_and_calls.md index 7dd0bc8e..4001ec2d 100644 --- a/skills/tsl-syntax-reference/references/05_functions_and_calls.md +++ b/skills/tsl-syntax-reference/references/05_functions_and_calls.md @@ -50,7 +50,8 @@ - 一旦某次调用里开始使用命名参数,后面的参数就不能再退回位置参数。 - 对二进制函数 / 系统函数直接使用命名参数,会报 `named parameter mode can't support here`;这类函数要先用 TSL 再封一层。 - 函数参数支持默认值。 -- 普通函数的默认值规则不要直接等同到 `unit interface` 声明;跨 `unit` 的默认参数边界只照本页最小反例和 [09_units_and_scope.md](09_units_and_scope.md) 处理。 +- `unit interface` 中的默认参数可以引用该接口中可访问的 `const`;实现函数头不要重复声明默认值。 +- 默认参数表达式属于新一代 TSL 能力;表达式里引用变量时,该变量按 `0` 求值。面向旧运行时或版本不明时,默认只生成字面量 / 可访问常量默认值。 - 尾部 `...` 形式的可变参数属于文档明确写法。 - 在可变参数函数体里,`Params`、`ParamCount`、`RealParamCount` 都可用。 - 可变参数组可以通过 `...` 转发给另一个函数调用。 @@ -61,6 +62,7 @@ - 匿名函数和 TSL 函数值的稳定调用方式仍是 `call(f, ...)` 或 `##f(...)`。 - `f(...)` 这种“函数变量直接调用”写法不作为可写事实;无论 `f` 是匿名函数、`findFunction(...)` 还是 `thisFunction(...)` 返回的函数指针,都不要默认写成直调。 - `::FuncName(...)` 可以指向全局/系统函数,用来绕过当前作用域里的同名局部函数。 +- `system.FuncName(...)` 专门强制指定系统函数;它与 `::FuncName(...)` 的全局限定语义不要互相替代。 - `external`、原生函数指针包装、`makeInstance` / C 回调和线程调用的事实见 [17_external_calls_and_threads.md](17_external_calls_and_threads.md)。 - 不要在 `.tsl` 的函数声明区之后继续追加脚本语句。 @@ -502,7 +504,7 @@ begin end; ``` -默认值也可以写成表达式: +新一代 TSL 的默认值也可以写成表达式: 代码块身份:可直接照写示例 @@ -523,24 +525,56 @@ end; - `Pack(a: 1)` 返回 `12` - `ExprDefault()` 返回 `3` -`unit interface` 声明下的默认参数要单独看。普通函数默认参数可用,不等于跨 `unit` 声明边界也同样可靠。 +表达式默认值的版本边界: -代码块身份:反例 / 不可照写 +代码块身份:可直接照写示例 -```text -unit UnitConst; -interface - -const default_value = 888; -function F(a, b = 100, c = default_value); +```tsl +function RefDefault(a, b = a + 1); +begin + return b; +end; ``` 边界说明: -- `F(1)` 输出 `101`,`F(1, 2)` 输出 `3` -- 同一组文件下,`UnitConst.default_value` 可读到 `888`,而 `F(1, 2, 3)` 输出 `6` -- 因此不要把“普通函数默认参数可用”直接泛化成“`unit interface` 里引用 `unit const` 的默认参数也同样可靠” -- 这类跨 `unit` 的声明边界事实见 [09_units_and_scope.md](09_units_and_scope.md) +- 在支持默认参数表达式的新一代 TSL 中,`RefDefault(5)` 返回 `1`,因为默认表达式里的变量 `a` 按 `0` 求值 +- 这项能力自 2025-08-27 后的 NG 客户端 / 新一代 TSL 服务端提供;目标版本不明时不要生成变量参与的默认表达式 + +`unit interface` 可以使用接口中可访问的常量作为默认值: + +代码块身份:配置片段 / 概念骨架 + +```text +// UnitDefaults.tsf +unit UnitDefaults; + +interface + +const default_value = 888; +function F(a, b = 100, c = default_value); + +implementation + +function F(a, b, c); +begin + return a + b + c; +end; + +end. + +// main.tsl +uses UnitDefaults; +echo F(1), ",", F(1, 2); +``` + +代码块身份:输出片段 + +```text +989,891 +``` + +说明:默认值只在 `interface` 声明处写一次;实现函数头使用同一组形参,但不重复 `= ...`。 ### 可变参数 `...` @@ -856,7 +890,26 @@ end; -`external`、原生函数指针包装、`makeInstance` / C 回调和线程调用,统一见 [17_external_calls_and_threads.md](17_external_calls_and_threads.md)。这一篇只保留“普通函数怎样定义和调用”的主线。 + + +系统函数可能与用户定义函数同名。需要明确指定系统实现时,使用 `system.FuncName(...)`: + +代码块身份:可直接照写示例 + +```tsl +value := system.strToInt("123"); +writeLn(value); +``` + +代码块身份:输出片段 + +```text +123 +``` + +本节只拥有 `system.` 这一调用限定语法。示例中的真实函数名、签名和目标 scope 必须由 `tsl-api-reference` skill 核对;不要从本节推断任意系统 API。 + +`external`、原生函数指针包装、`makeInstance` / C 回调和线程调用,统一见 [17_external_calls_and_threads.md](17_external_calls_and_threads.md)。 ## 默认生成模板 @@ -911,7 +964,10 @@ end; - 把 `a = 1` 这种比较表达式误当成命名参数调用。 - 以为默认值只能用于无类型参数。 - 在 `const` 形参上直接赋值。 -- 把普通函数的默认值规则原样套到 `unit interface` 里的 `const` 默认参数上。 +- 在旧运行时或版本不明时生成默认参数表达式。 +- 以为默认参数表达式里的形参会取本次调用实参;其中变量按 `0` 求值。 +- 在 `unit interface` 和 `implementation` 的函数头上重复写默认值。 +- 用 `::FuncName(...)` 和 `system.FuncName(...)` 互相替代,而不区分全局限定与系统函数限定。 - 把匿名函数或 `findFunction(...)` 返回值默认写成 `f(...)` 直调。 - 把命名参数直接套到二进制函数或系统函数上。 - 在一次调用里先进入命名参数模式,后面又退回位置参数。 @@ -1000,7 +1056,7 @@ const default_value = 888; function F(a, b = 100, c = default_value); ``` -不要把上面这种 `unit interface` 声明直接当成已经等价于普通函数默认参数规则。按文档结果,对应的 `F(1)` 输出是 `101`,不是按 `default_value = 888` 补成的结果;具体边界见 [09_units_and_scope.md](09_units_and_scope.md)。 +上面的片段缺少 `implementation` 和 `end.`,因此不能作为完整 `unit` 文件直接照写;它不是“接口常量不能作为默认值”的反例。完整可写结构见本页默认参数章节。 代码块身份:反例 / 不可照写 diff --git a/skills/tsl-syntax-reference/references/06_expressions_and_operators.md b/skills/tsl-syntax-reference/references/06_expressions_and_operators.md index a65c4e86..d41e071f 100644 --- a/skills/tsl-syntax-reference/references/06_expressions_and_operators.md +++ b/skills/tsl-syntax-reference/references/06_expressions_and_operators.md @@ -101,6 +101,7 @@ - 连续标量比较才用 `:>`、`:<`、`:<>`、`:==`、`:>=`、`:<=`。 - 数组逐元素链式比较才用 `::>`、`::<`、`::<>`、`::==`、`::>=`、`::<=`。 - 混合两类以上运算符时,优先用括号明确分组,不依赖跨语言记忆里的优先级。 +- TSL 的主要优先级从高到低是:成员/下标/调用,`not`、前置自增减与倒数/逆等一元运算,字符串 `$`,幂,乘除移位,加减(包括一元正负号)与集合/位运算,比较与 `in`/`like`/`is`,`and`/`or`,冒号,赋值,表达式前导 `@`;同级通常从左到右。 边界规则: @@ -566,6 +567,113 @@ writeLn(if 2 > 1 then 2 else 1); `if condition then true_value else false_value` 必须带 `else`,否则不是本页可照写的表达式形态。 +### 运算符优先级 + + + + + +下表是面向当前正式文档已收录运算符的保守分组,数字越小优先级越高: + + +| 级别 | 主要形态 | 说明 | +| --- | --- | --- | +| 0 | `()`、`[]`、`.`、`?.`、函数调用 | 分组、访问、下标与调用最先结合。 | +| 1 | `not`、前置 `++` / `--`、`!`、`.!`、`.!!` | 逻辑非、前置自增减与倒数/逆等一元运算。 | +| 2 | `$` | 字符串连接。 | +| 3 | `^`、`~`、`:^` | 幂、开方与对应矩阵形态。 | +| 4 | `*`、`/`、`\`、`%`、`div`、`mod`、`shl`、`shr`、`rol`、`ror` | 乘除、取余与移位。 | +| 5 | `+`、`-`、一元正号 / 负号、集合运算、点前缀位/逻辑运算 | 加减、一元正负号及同组语言运算;因此幂先于一元正负号结合,例如 `-2 ^ 2` 等于 `-(2 ^ 2)`。 | +| 6 | 比较、`is`、`in`、`sqlin`、`like`、链式比较 | 关系判断。 | +| 7 | `and`、`or` | 低于比较,因此 `a > 1 and b < 2` 按两个比较再逻辑与理解。 | +| 8 | `:` | 冒号相关表达式形态。 | +| 9 | `:=` 与各类复合赋值 | 赋值接近最低优先级。 | +| 10 | `@` | 表达式对象前导最低。 | + + +同级运算通常从左到右求值。为了避免不同语言之间的优先级记忆混淆,混合两类以上运算符时仍推荐显式加括号: + +代码块身份:可直接照写示例 + +```tsl +value := 1 + 2 * 3; +signedPower := -2 ^ 2; +flag := (2 > 1) and (1 < 2); +writeLn(value); +writeLn(signedPower); +writeLn(flag); +``` + +代码块身份:输出片段 + +```text +7 +-4 +1 +``` + +稀有矩阵运算符的详细优先级以其专题页为准;不要用本表外推尚未写入正式文档的符号。 + +### 静态计算表达式 `static` + + + + + +表达式前的 `static` 会缓存第一次计算结果。带 `name` 时,名称表达式是缓存键:同一键复用第一次结果,不同键分别计算。 + +代码块身份:可直接照写示例 + +```tsl +echo StaticValue(), ",", StaticValue(); + +function StaticValue(); +begin + return static NextValue(); +end; + +function NextValue(); +begin + global static_calls; + static_calls += 1; + return static_calls; +end; +``` + +代码块身份:输出片段 + +```text +1,1 +``` + +按键分别缓存: + +代码块身份:可直接照写示例 + +```tsl +echo NamedValue("A"), ",", NamedValue("A"), ",", NamedValue("B"); + +function NamedValue(key); +begin + return static NextValue() name key; +end; + +function NextValue(); +begin + global named_static_calls; + named_static_calls += 1; + return named_static_calls; +end; +``` + +代码块身份:输出片段 + +```text +1,1,2 +``` + +这不是类成员的 `static` 字段;类静态成员见 [08_objects_and_classes.md](08_objects_and_classes.md)。缓存结果具有运行时状态,不要用它保存每次调用都必须重新计算的值。 + ### 表达式对象 @@ -847,6 +955,8 @@ writeLn(value); - 把 `if` 表达式写成没有 `else` 的半句。 - 把本页明确的 `c?.a?.[1]` 外推成所有深链式空安全访问都可靠。 - 从其他语言推断 TSL 运算符能力。 +- 把类成员 `static field;` 与表达式前导 `static Expression [name Key]` 当成同一种语法。 +- 在复杂混合表达式里依赖其他语言的优先级记忆而省略括号。 代码块身份:反例 / 不可照写 diff --git a/skills/tsl-syntax-reference/references/07_control_flow.md b/skills/tsl-syntax-reference/references/07_control_flow.md index 1b54aaee..54777a8b 100644 --- a/skills/tsl-syntax-reference/references/07_control_flow.md +++ b/skills/tsl-syntax-reference/references/07_control_flow.md @@ -16,6 +16,8 @@ - 块式分支内部的普通语句必须用分号结尾。 - 控制流块的 `begin ... end` 后可以加分号也可以不加(语法都允许)。 - `for` 支持 `to`、`downto`、可选 `step`,以及 `for i, v in array` 遍历。 +- 计数 `for` 的初值、终值和步长确定后,循环次数随之固定;循环体内不要给控制变量赋值。 +- `for i, v in array` 遍历期间,不要修改被遍历数组或其中元素。 - `while` 和 `repeat ... until` 都可直接使用;`repeat` 至少会先执行一轮再判断结束条件。 - `break` 会跳出当前最近一层循环,`continue` 会跳过当前轮剩余语句。 - `case ... of ... else ... end` 可作为语句形态生成;`end` 后可以加分号也可以不加。 @@ -154,6 +156,7 @@ for i, value in numbers do - 依次输出 `10`、`120`、`230` - 这说明 `for i, value in numbers` 里的 `i` 从 `0` 开始 +- 遍历期间把 `numbers` 当成只读集合;需要修改时先遍历副本,或在循环结束后统一写回 代码块身份:输出片段 @@ -495,6 +498,8 @@ end - 以为 `try ... finally` 会吞掉异常。 - 在还没搞清表达式规则前,先把复杂业务函数塞进条件里。 - 把控制流问题和函数文件模型问题混在一起排查。 +- 在计数 `for` 循环体里给控制变量赋值。 +- 在 `for ... in` 遍历期间修改被遍历数组或其中元素。 代码块身份:反例 / 不可照写 diff --git a/skills/tsl-syntax-reference/references/10_runtime_context_and_with.md b/skills/tsl-syntax-reference/references/10_runtime_context_and_with.md index 6f555312..efaa9171 100644 --- a/skills/tsl-syntax-reference/references/10_runtime_context_and_with.md +++ b/skills/tsl-syntax-reference/references/10_runtime_context_and_with.md @@ -18,7 +18,7 @@ - `setSysParam(key, value)` 和 `getSysParam(key)` 可以直接用字符串键。 - `sysParams[key]` 可以直接读写这些运行时参数。 - 块环境语句可写成 `with *, sys_param_values do begin ... end` 或 `with **, sys_param_values do begin ... end`。 -- `with *` 会把提供的系统参数合并进当前运行时上下文;不要依赖它在块结束后自动恢复外层值。 +- `with *` 会把提供的系统参数合并进当前运行时上下文。普通自定义键可能在块后保留新值;特殊系统环境变量会在块结束后恢复,因此不要把两类键的恢复行为混为一谈。 - `with **` 会用提供的系统参数建立隔离块环境;块结束后恢复外层系统参数。 - 后缀 `with` 形式写在函数文件调用后面:`#Func() with array(...)`。 - `with array(...)` 只在该次调用里临时覆盖对应键,调用结束后会恢复外部原值。 @@ -110,7 +110,8 @@ writeLn(getSysParam("b")); - 块内输出 `2`、`3` - 块后输出 `2`、`3` -- 说明 `with *` 会把传入键合并进当前系统参数上下文;不要把它当成自动恢复外层值的隔离块 +- 这组实验只证明普通自定义键 `"a"` / `"b"` 会合并并在块后保留新值 +- 特殊系统环境变量在块结束后会恢复;不要用普通字符串键的结果外推股票、日期等特殊环境 `with *, SysParamArray do` 使用当前所有系统参数: @@ -443,7 +444,7 @@ end; - 把系统参数页直接写成金融函数页。 - 把 `#Func() with array(...)` 误判成也能直接套在本地函数 `Demo()` 后面。 -- 把 `with *` 误判成会自动恢复外层系统参数。 +- 以为 `with *` 对所有键都统一“不恢复”或统一“恢复”;普通自定义键与特殊系统环境变量的边界不同。 - 以为 `with array(...)` 改的是全局永久值,不会恢复外层原环境。 - 把网格句柄直接当最终值用,而不做 `dupvalue(...)`。 - 以为从全局缓存取出的值,本地写入后仍然保持缓存身份。 diff --git a/skills/tsl-syntax-reference/references/11_matrix_and_collections.md b/skills/tsl-syntax-reference/references/11_matrix_and_collections.md index ab29c5e5..f78006a8 100644 --- a/skills/tsl-syntax-reference/references/11_matrix_and_collections.md +++ b/skills/tsl-syntax-reference/references/11_matrix_and_collections.md @@ -31,7 +31,7 @@ - + 顺序数组与字符串键表: @@ -299,7 +299,7 @@ writeLn("子集 (1,0):", subset[1][0]); - + 基础算符作用于非完全矩阵(行长度不一致或字符串键不对齐的数组)时,对应位置不存在或为 `nil` 时**默认当 0 处理**: @@ -356,6 +356,32 @@ writeLn("(1,1):", result[1][1]); - `matrix_value + 10` 每个元素都加 10 - 这些是逐元素运算(element-wise),区别于矩阵乘法 `:*`,见 [21_matrix_deep_dive.md](21_matrix_deep_dive.md) +二维矩阵与一维数组做基础算术时,一维数组按“行”广播;它的长度必须等于矩阵行数: + +代码块身份:可直接照写示例 + +```tsl +matrix_value := array((1, 2, 3), (4, 5, 6)); +row_factors := array(10, 100); +left_result := matrix_value * row_factors; +right_result := row_factors * matrix_value; +writeLn(left_result[0][0]); +writeLn(left_result[1][2]); +writeLn(right_result[0][1]); +writeLn(right_result[1][0]); +``` + +代码块身份:输出片段 + +```text +10 +600 +20 +400 +``` + +说明:第一行使用 `10`,第二行使用 `100`;左右操作数交换后仍按行广播。不要把这条规则误写成按列广播。 + ## 本页不生成的范围 @@ -383,3 +409,4 @@ writeLn("(1,1):", result[1][1]); - 不要把普通 `array(...)` 自动升级成 `FMArray`;`FMArray` 专属事实见 [22_fmarray.md](22_fmarray.md)。 - 不要把点前缀比较 `.>` 和矩阵链式比较 `::>` 混用;`.>` 返回逻辑数组,`::>` 是链式比较。 - 不要以为非完全矩阵缺位会报错;默认当 `0` 处理。 +- 二维矩阵与一维数组运算时,不要把一维数组当成按列因子;它按行广播且长度要匹配行数。 diff --git a/skills/tsl-syntax-reference/references/13_ts_sql.md b/skills/tsl-syntax-reference/references/13_ts_sql.md index 5ce3f011..a61caf3b 100644 --- a/skills/tsl-syntax-reference/references/13_ts_sql.md +++ b/skills/tsl-syntax-reference/references/13_ts_sql.md @@ -19,8 +19,9 @@ - 在一维数组上做 TS-SQL 时,优先使用 `thisRow` 和 `thisRowIndex`。 - `select` 返回二维结果,`sselect` 返回一维结果,`vselect` 返回单值,`mselect` 返回 `Matrix`。 - `where`、`group by`、`order by` 可以直接接在 `from` 后面继续使用;`order by` 支持 `asc`/`desc` 与多列逗号分隔。 -- 分组后按聚集条件筛选用 `having`(`where` 不能用聚集);`having` 里用 `countof([字段])` 或 `countof(1)`,不要用 `countof(*)`。 +- 分组后按聚集条件筛选用 `having`(`where` 不能用聚集);计数可用 `countof([字段])`、`countof(1)`、`countof()`,也可用带空格的 `countof( * )`。无空格的 `countof(*)` 会与块注释起始符 `(*` 冲突。 - 多表 `join` 时,字段访问应写成 `[表序号].["字段名"]`;`on` 可用 `and` 写多条件;`[表序号].*` 取整表列。 +- 多表联接中,`thisRow(表序号)` 和 `thisRowIndex(表序号)` 分别取得指定来源表的当前整行与原始下标。 - 联接类型:`left join` 保留左表、`right join` 保留右表、`full join` 保留双方、`cross join` 笛卡尔积、逗号联接等价于 `cross join`;不匹配处用 `nil` 填充。 - `select` 列表支持 `distinct` 去重、`as 别名`、`as nil`(参与计算但不返回)、`起始列 to 结束列` 字段区间、`selectopt(位选项)`、`drange(区间/M of N)`。 - 聚集函数统一形态 `Func(Expr[, Cond[, N[, MovingFirst[, CacheId]]]])`:条件聚集、移动聚集、多字段聚集、`refof(Expr, N)` 引用相对行;`aggof('名', Expr)` 调用自定义聚集回调。 @@ -233,6 +234,23 @@ writeLn(join_result[0]["V2"]); 100 ``` +联接上下文里的指定来源当前行: + +代码块身份:可直接照写示例 + +```tsl +left_rows := array(("id": 1, "v": 10), ("id": 2, "v": 20)); +right_rows := array(("id": 2, "v": 20), ("id": 3, "v": 30)); +join_rows := select thisRow(1) as "LeftRow", + thisRow(2) as "RightRow", + thisRowIndex(1) as "LeftIndex", + thisRowIndex(2) as "RightIndex" + from left_rows join right_rows on [1].["id"] = [2].["id"] + end; +``` + +结果说明:唯一匹配行中,`LeftRow["v"] = 20`、`RightRow["v"] = 20`、`LeftIndex = 1`、`RightIndex = 0`。 + ### `thisGroup` @@ -773,6 +791,29 @@ writeLn(ref_prev[1]["Expr1"]); - 移动聚集:`avgof(表达式, 条件, N, MovingFirst)` 取当前行往前 N 条的滑动统计 - `refof(表达式, N)` 引用前 N 行的值(`N` 为负则往后);首行无前值时返回 `0` +`*` 作为聚集输入时必须和左括号留空格,避免 `(*` 被词法层识别为块注释: + +代码块身份:可直接照写示例 + +```tsl +a := array(("x": 1, "y": 10), ("x": 3, "y": 20)); +row_count := vselect countof( * ) from a end; +column_avg := select avgof( * ) from a end; +writeLn(row_count); +writeLn(column_avg[0]["Expr1"]); +writeLn(column_avg[0]["Expr2"]); +``` + +代码块身份:输出片段 + +```text +2 +2.0 +15.0 +``` + +说明:`avgof( * )` 对每列分别聚集;这里两列平均值依次为 `2`、`15`。 + ### `group by ... having` @@ -800,7 +841,7 @@ A 说明: - `having 聚集条件` 在分组后筛选(上例只保留成员数大于 1 的 `A` 组) -- `having` 里的计数用 `countof([字段])` 或 `countof(1)` +- `having` 里的计数优先用 `countof([字段])`、`countof(1)` 或 `countof()`;确需星号时写成 `countof( * )` 代码块身份:反例 / 不可照写 @@ -808,7 +849,7 @@ A having_rows := select ["cls"] from a group by ["cls"] having countof(*) > 1 end; ``` -`countof(*)` 这种带 `*` 的写法不成立,会报 `CountOf ( not found`。计数改用 `countof([字段])` 或 `countof(1)`。 +无空格的 `countof(*)` 会把 `(*` 词法组合解释成块注释开头,随后报 `CountOf ( not found`。改用 `countof( * )`、`countof()`、`countof([字段])` 或 `countof(1)`。 ### `thisOrder` 与多列 `order by` @@ -967,7 +1008,7 @@ query_result := select * from source_rows end; - 在 `left join` 时省略 `on` 子句或不用 `[表序号].["字段"]` 形式。 - 在 `insert` 时漏掉 `insertfields` 或字段数与值数不匹配。 - 期望 `update`/`delete` 返回新数组;它们直接修改原数组。 -- 用 `countof(*)` 数行数;`*` 星号形式不被支持,改用 `countof([字段])` 或 `countof(1)`。 +- 写无空格的 `countof(*)`,使 `(*` 与块注释起始符冲突;改用 `countof( * )`、`countof()`、`countof([字段])` 或 `countof(1)`。 代码块身份:反例 / 不可照写 @@ -975,7 +1016,7 @@ query_result := select * from source_rows end; n := vselect countof(*) from source_rows end; ``` -`countof(*)` 会报 `CountOf ( not found`。数行数改用 `countof([字段])` 或 `countof(1)`。 +`countof(*)` 会报 `CountOf ( not found`,原因是 `(*` 与块注释起始符冲突。星号写法加空格为 `countof( * )`,或改用 `countof()` / 明确表达式。 代码块身份:反例 / 不可照写 diff --git a/skills/tsl-syntax-reference/references/14_debug_and_profiler.md b/skills/tsl-syntax-reference/references/14_debug_and_profiler.md index 3cbd0ea1..5d3eb7b7 100644 --- a/skills/tsl-syntax-reference/references/14_debug_and_profiler.md +++ b/skills/tsl-syntax-reference/references/14_debug_and_profiler.md @@ -6,7 +6,7 @@ -回答“`goto`、`debugReturn`、`debugRunEnv`、`mtic` / `mtoc`、`setProfiler`、`__line__` 和 `__stack_frame` 怎样写、会怎样表现”。 +回答“`goto`、`debugReturn`、`debugRunEnv`、`mtic` / `mtoc`、`setProfiler`、内存伪变量、`__line__` 和 `__stack_frame` 怎样写、会怎样表现”。 ## 核心规则 @@ -24,6 +24,7 @@ - `setProfiler(7)` 配合 `getProfilerInfo(1)`,可以在不弹窗的情况下拿到性能分析器信息。 - `__line__` 会返回所在代码行号。 - `__stack_frame` 会返回调用栈帧数组;最小 `toStn(...)` 观察结果里,每一项是 `(line, "function")` 这一类二元组。 +- `_myMem_` 表示应用当前已使用内存,`_maxMem_` 表示应用允许使用的最大内存;两者是只读数值伪变量。单位和 `_maxMem_` 的具体取值由宿主环境决定。 ## 可直接照写示例 @@ -217,6 +218,32 @@ writeLn(length(info) > 0); - 说明 `setProfiler(7)` 可以开启性能分析器统计 - 说明 `getProfilerInfo(1)` 会直接返回性能分析器信息,而且结果是非空数组 +### 内存伪变量 `_myMem_` / `_maxMem_` + + + + + +代码块身份:可直接照写示例 + +```tsl +writeLn(ifNumber(_myMem_)); +writeLn(ifNumber(_maxMem_)); +``` + +代码块身份:输出片段 + +```text +1 +1 +``` + +说明: + +- `_myMem_` 是应用已使用内存的数值 +- `_maxMem_` 是宿主允许应用使用的最大内存数值;某些环境可能返回 `0` 表示未给出可比较的上限 +- 不要假设 `_maxMem_ >= _myMem_`,也不要在没有宿主文档时写死单位 + ### `__line__` 与 `__stack_frame` @@ -276,3 +303,4 @@ array( - 不要假设 `goto` 可以跨函数、跨脚本体或跳到单独成行的 `label`。 - 不要给计时或性能分析器调用补未写入文档参数。 - 不要把调试客户端副作用写成普通输出事实。 +- 不要假定 `_maxMem_` 总是非零、总是大于 `_myMem_`,或擅自指定内存单位。 diff --git a/skills/tsl-syntax-reference/references/15_lexical_structure_and_compile_options.md b/skills/tsl-syntax-reference/references/15_lexical_structure_and_compile_options.md index 8eabad11..c182000d 100644 --- a/skills/tsl-syntax-reference/references/15_lexical_structure_and_compile_options.md +++ b/skills/tsl-syntax-reference/references/15_lexical_structure_and_compile_options.md @@ -13,7 +13,7 @@ - 标识符大小写无关;下划线可出现在标识符中。 -- `//` 是行注释;首行 `#!` 可作为 CGI 风格注释;`{ ... }` 与 `(* ... *)` 是块注释。 +- `//` 是行注释;首行 `#!` 可作为 CGI 风格注释;`{ ... }` 与 `(* ... *)` 是块注释。两种块注释可以交错嵌套,同类块注释不能嵌套。 - 条件编译指令使用 `{$define}`、`{$undef}`、`{$ifdef}`、`{$ifndef}`、`{$else}`、`{$endif}`。 - 条件编译只编译命中的分支;未命中的分支不参与脚本编译。 - `{$explicit+}` 开启后,后续变量必须先用 `var` 声明;`{$explicit-}` 可以在同一源文件里重新关闭这个要求。 @@ -58,7 +58,7 @@ TSL 关键字大小写无关;本表统一按文档推荐写法展示。生成 - + 大小写无关与下划线标识符: @@ -112,6 +112,8 @@ writeLn(40); - 依次输出 `1`、`10`、`30` - 说明首行 `#!`、`//`、`{ ... }`、`(* ... *)` 都属于文档明确注释形态 +- 说明外层 `{ ... }` 可以包含 `(* ... *)`;反向交错也可用 +- 同类 `{ { ... } }` 或 `(* (* ... *) *)` 不构成嵌套注释,会产生语法错误 - 说明 `define` / `undef` / `ifdef` / `ifndef` / `else` / `endif` 这一组条件编译指令可以正常生效 ### 显式变量声明开关 @@ -184,27 +186,13 @@ writeLn(1); -`{$CompileOption}` 用于设置编译期开关,改变编译器的默认行为: +TSL 的编译选项使用 `{$Option+}` / `{$Option-}` 一类指令改变后续源码的编译方式。本页只拥有已经分别验证并有专题规则的选项: -代码块身份:可直接照写示例 +- `{$explicit+}` / `{$explicit-}`:切换变量是否必须预先声明,见上一节 +- `{$varByRef+}` / `{$varByRef-}`:切换未修饰形参的默认传递方式,见下一节 +- `{$ifdef ...}` 等条件编译指令:控制分支是否参与编译 -```tsl -{$CompileOption optimize=1} -echo 1 + 1; -``` - -代码块身份:输出片段 - -```text -2 -``` - -说明: - -- `{$CompileOption optimize=1}` 开启优化 -- 编译选项从出现位置开始生效,直到源文件结束或被其他选项覆盖 -- 常见选项包括 `optimize`、`buffermode`、`DebugInfo` 等 -- 编译选项细节以项目工具链和实际编译命令为准。 +`optimize`、`buffermode`、`DebugInfo` 等没有在本 skill 中形成可验证语义,不作为正式可生成选项。不要用“脚本仍能输出结果”来证明某个未知编译选项确实生效。 ### 参数默认传递开关 @@ -263,6 +251,8 @@ end; - 不要在 `{$explicit+}` 后继续直接使用未声明变量。 +- 不要同类嵌套 `{ ... }` 或 `(* ... *)` 块注释;需要嵌套时交错使用两种定界符。 +- 不要生成未在本页形成可验证规则的 `{$CompileOption optimize=...}`、`buffermode` 或 `DebugInfo`。 - 不要把 `{$i ...}` / `{$include ...}` 包含文件写法当成可用能力。 - 不要把 `反例 / 不可照写` 代码块复制进正向示例。 diff --git a/skills/tsl-syntax-reference/references/18_namespace_libpath_and_unit_runtime.md b/skills/tsl-syntax-reference/references/18_namespace_libpath_and_unit_runtime.md index 58479a2a..96b2d6a9 100644 --- a/skills/tsl-syntax-reference/references/18_namespace_libpath_and_unit_runtime.md +++ b/skills/tsl-syntax-reference/references/18_namespace_libpath_and_unit_runtime.md @@ -22,7 +22,7 @@ - 完整 `unit` 形态可以包含 `interface`、`implementation`、`initialization`、`finalization`,并以 `end.` 结束。 - `initialization` 在 `unit` 第一次被实际使用时触发,不是只因为顶层写了 `uses` 就立刻执行。 - `finalization` 会在脚本结束前触发。 -- 直接写 `DemoUnit.Member` 时,可以读到 `interface` 和 `implementation` 里的常量、变量。 +- 跨版本安全边界只保证 `interface` 中声明的常量、变量和函数可由引用者访问;只在 `implementation` 中声明的成员按私有内容处理。 - `findFunction("DemoUnit")` 拿到的是 `unit` 对象入口;本页只把它稳定暴露 `interface` 成员写成文档事实。 - `DemoUnit.var_name := value` 这种限定赋值不作为可写事实;如果要改 `unit` 状态,应导出函数或方法来改。 - `tslfilename()` 的参数规格使用 `tsl-api-reference` skill 按名查询;本页只保留它返回正在执行的 `.tsl` 主脚本完整路径这一行为事实。 @@ -111,7 +111,7 @@ FINAL -直接限定读取: +跨版本安全的限定读取: 代码块身份:配置片段 / 概念骨架 @@ -149,8 +149,6 @@ uses DemoUnit; writeLn(DemoUnit.public_const); writeLn(DemoUnit.public_var); -writeLn(DemoUnit.impl_const); -writeLn(DemoUnit.impl_var); writeLn(PublicFunc()); ``` @@ -158,10 +156,10 @@ writeLn(PublicFunc()); - `DemoUnit.public_const` 输出 `1`。 - `DemoUnit.public_var` 输出 `3`。 -- `DemoUnit.impl_const` 输出 `2`。 -- `DemoUnit.impl_var` 输出 `4`。 - `PublicFunc()` 输出 `10`。 -- 本页文档边界是:实现段函数仍私有,但实现段常量和变量可以通过 `DemoUnit.Member` 直接读取。 +- 本页的跨版本文档边界是:外部只依赖 `interface` 引出的成员;实现段里的常量、变量和函数都视为私有。 + +部分新一代解释器允许用 `DemoUnit.impl_const` / `DemoUnit.impl_var` 限定读取实现段数据,但这与经典 `unit` 可见性规则冲突,不作为跨环境默认生成能力。若项目已经依赖该行为,必须先按目标解释器实测并记录版本。 实现段函数的外部调用反例: @@ -491,7 +489,7 @@ writeLn(Hello()); - 把 `DemoUnit.var_name := value` 当成可用的限定赋值。 -- 以为 `implementation` 里的常量和变量一定都不能从 `DemoUnit.Member` 读到。 +- 默认从外部读取只在 `implementation` 中声明的成员;跨版本安全代码应通过 `interface` 导出。 - 以为 `findFunction("DemoUnit")` 暴露的成员范围和 `DemoUnit.Member` 完全相同。 - 把脚本内的 `namespace "..."` 当成和 `tsl.conf` 里的 `Namespace=...` 叠加,而不是覆盖。 - 把 `-LIBPATH` 放在脚本文件名前面。 diff --git a/skills/tsl-syntax-reference/references/22_fmarray.md b/skills/tsl-syntax-reference/references/22_fmarray.md index 1fa36cc1..fce35a6b 100644 --- a/skills/tsl-syntax-reference/references/22_fmarray.md +++ b/skills/tsl-syntax-reference/references/22_fmarray.md @@ -36,7 +36,7 @@ - + 代码块身份:可直接照写示例 diff --git a/skills/tsl-syntax-reference/scripts/lookup.py b/skills/tsl-syntax-reference/scripts/lookup.py index 608c19cb..25da24af 100644 --- a/skills/tsl-syntax-reference/scripts/lookup.py +++ b/skills/tsl-syntax-reference/scripts/lookup.py @@ -41,6 +41,7 @@ STRUCTURAL_METADATA_RE = re.compile( r"", re.DOTALL | re.IGNORECASE, ) +HTML_COMMENT_RE = re.compile(r"", re.DOTALL) IDENTITY_PREFIX = "代码块身份:" BLOCK_DESCRIPTION_PREFIX = "代码块说明:" ALLOWED_IDENTITIES = { @@ -156,7 +157,6 @@ CHINESE_QUERY_PARTICLES = ("的", "是", "吗", "呢", "吧") ASCII_FILTER_STOP_TOKENS = { "debug", "please", - "program", "tinysoft", "tsl", "tsf", @@ -480,7 +480,7 @@ def load_sections(references_dir: Path = DEFAULT_REFERENCES_DIR) -> list[Section raise ReferenceStructureError(f"重复 section ID:{base_id}") seen_ids.add(base_id) tags = _section_tags(local_body) - searchable_body = STRUCTURAL_METADATA_RE.sub(" ", local_body) + searchable_body = HTML_COMMENT_RE.sub(" ", local_body) searchable_text = normalize( "\n".join((page.stem, page_title, *heading_path, *tags, searchable_body)) ) @@ -1122,9 +1122,9 @@ def _text_contains_exact_query(text: str, query: str) -> bool: def _intent_score(section: Section, query: str) -> int: aliases = PAGE_INTENT_ALIASES.get(section.page.name, ()) - return PAGE_INTENT_SCORE * sum( + return PAGE_INTENT_SCORE if any( _query_contains_phrase(query, alias) for alias in aliases - ) + ) else 0 def _has_chinese_context(section: Section, query: str) -> bool: @@ -1161,7 +1161,9 @@ def _tag_matched_tokens(tags: tuple[str, ...], query_token_set: set[str]) -> int def _code_text(body: str) -> str: inline = INLINE_CODE_RE.findall(body) fenced = FENCED_CODE_RE.findall(body) - return normalize("\n".join((*inline, *fenced))) + # 标识符信号只来自 ASCII 代码术语。中文散文会走标题、tag 和正文得分; + # 若把围栏里的“下标数组”等输出标签也当标识符,中文查询会被样例值劫持。 + return normalize("\n".join(ASCII_TOKEN_RE.findall("\n".join((*inline, *fenced))))) def _score_section(section: Section, query: str, mode: str) -> ScoreBreakdown: @@ -1169,8 +1171,7 @@ def _score_section(section: Section, query: str, mode: str) -> ScoreBreakdown: tokens = query_tokens(query) heading_text = normalize("\n".join(section.heading_path)) page_title_text = normalize(section.page_title) - body_without_metadata = STRUCTURAL_METADATA_RE.sub(" ", section.local_body) - body_text = normalize(SECTION_TAG_RE.sub(" ", body_without_metadata)) + body_text = normalize(HTML_COMMENT_RE.sub(" ", section.local_body)) tag_text = normalize("\n".join(section.tags)) term_text = _code_text(section.local_body) expanded_only_tokens = _synonym_tokens(query) - _base_query_tokens(query) @@ -1356,6 +1357,7 @@ def query_sections( ) ranked.sort( key=lambda match: ( + match.weak, -match.score, *(-value for value in match.priority), match.section.page.as_posix(), @@ -1400,10 +1402,9 @@ def _safe_json_string(value: str) -> str: def _plain_text_summary(body: str, limit: int = 180) -> str: - # 标签是检索元数据,不是事实正文;不能泄进候选摘要。 - without_metadata = STRUCTURAL_METADATA_RE.sub(" ", body) - without_tags = SECTION_TAG_RE.sub(" ", without_metadata) - without_fences = FENCED_CODE_RE.sub(" ", without_tags) + # HTML 注释都是维护元数据,不是事实正文;不能泄进候选摘要。 + without_comments = HTML_COMMENT_RE.sub(" ", body) + without_fences = FENCED_CODE_RE.sub(" ", without_comments) without_links = re.sub( r"!?\[([^\]]*)\]\([^)]+\)", lambda match: match.group(1), without_fences ) @@ -1464,7 +1465,7 @@ def render_candidates(result: QueryResult) -> str: def render_section(section: Section) -> str: - body = STRUCTURAL_METADATA_RE.sub("", section.body) + body = HTML_COMMENT_RE.sub("", section.body) body = re.sub(r"\n{3,}", "\n\n", body).rstrip() lines = [ "# TSL Syntax Section", diff --git a/test/test_tsl_syntax_reference.py b/test/test_tsl_syntax_reference.py index 7f4d7455..0ebd8d73 100644 --- a/test/test_tsl_syntax_reference.py +++ b/test/test_tsl_syntax_reference.py @@ -298,8 +298,8 @@ class TslSyntaxReferenceTests(unittest.TestCase): for query in ("数组下标", "请帮我解释数组的下标"): with self.subTest(query=query): result = lookup.query_sections(query, "explain", limit=1) - self.assertEqual("11_matrix_and_collections.md", result.matches[0].section.page.name) - self.assertEqual("基础数组与键表", result.matches[0].section.heading_path[-1]) + self.assertEqual("03_values_and_literals.md", result.matches[0].section.page.name) + self.assertEqual("核心规则", result.matches[0].section.heading_path[-1]) def test_documented_write_and_diagnose_queries_rank_expected_sections(self): write_result = lookup.query_sections("命名参数", "write", limit=1) @@ -349,6 +349,53 @@ class TslSyntaxReferenceTests(unittest.TestCase): self.assertEqual(2, result.returncode) self.assertIn("only weak candidates", result.stderr) + def test_strong_candidates_are_ranked_before_weak_diagnose_boosts(self): + result = lookup.query_sections("变量赋值", "diagnose", limit=1) + + self.assertTrue(result.matches) + self.assertFalse(result.matches[0].weak) + self.assertNotEqual("syntax-04-008", result.matches[0].section.id) + + def test_page_intent_aliases_score_once_per_page(self): + result = lookup.query_sections("高性能矩阵 fmarray", "explain", limit=3) + + self.assertTrue(result.matches) + self.assertEqual("syntax-22-004", result.matches[0].section.id) + for match in result.matches: + self.assertNotIn("intent=160", match.reasons) + self.assertIn("intent=80", result.matches[0].reasons) + + def test_program_is_a_searchable_language_keyword(self): + result = lookup.query_sections("PROGRAM", "explain", limit=1) + + self.assertTrue(result.matches) + self.assertEqual("syntax-02-009", result.matches[0].section.id) + + def test_maintenance_html_comments_do_not_leak_into_lookup_output(self): + result = run_lookup("--query", "PROGRAM", "--mode", "explain") + + self.assertEqual(0, result.returncode) + self.assertNotIn("prettier-ignore", result.stdout) + + def test_new_language_gaps_have_stable_retrieval_entries(self): + cases = { + "全局变量": "syntax-04-009", + "运行时常量": "syntax-04-010", + "静态计算": "syntax-06-016", + "只计算一次": "syntax-06-016", + "指定系统函数": "syntax-05-013", + "运算符优先级": "syntax-06-015", + "内存上限": "syntax-14-011", + "嵌套注释": "syntax-15-005", + "JOIN 第二张表当前行": "syntax-13-010", + "按行广播": "syntax-11-011", + } + for query, expected_id in cases.items(): + with self.subTest(query=query): + result = lookup.query_sections(query, "explain", limit=1) + self.assertTrue(result.matches, query) + self.assertEqual(expected_id, result.matches[0].section.id) + def test_batch_section_retrieval_is_atomic(self): ids = [section.id for section in lookup.load_sections()[:2]] success = run_lookup("--section", *ids)